То, что не описано в статье, но всех интересует
От: help-me  
Дата: 05.04.12 09:00
Оценка: :))

Операция разыменования становится возможной исключительно в силу типизации указателей: в типе заложена информация о размере памяти, необходимой для размещения объекта, и о способе интерпретации содержащихся в ней данных, что в совокупности с адресом однозначно идентифицирует объект в памяти.

Получается, любая переменная (или указатель на переменную) кроме адреса должен хранить и тип(размер) переменной, чтобы при создании, удалении или чтении\перезаписи значения этой переменной использовать ровно столько байт, сколько есть в переменной, не больше и не меньше (но тогда на x86 размер указателя или самой переменной должен быть не 4байта(только адрес), а больше?)?

Адрес объекта является той характеристикой, которая идентифицирует объект, отличает один объект от всех других объектов в системе.

В первой цитате написано, что не только адрес, но и тип(размер) однозначно идентифицируют, тогда почему во второй цитате говорится только про адрес?

Кроме того, размер стека в большинстве случаев ограничен (для программ, разрабатываемых в среде Microsoft Visual Studio, размер стека по умолчанию — 1 Мб), что приводит к невозможности размещения в нём больших объектов, к примеру, тех же массивов.

это только в с++ ? в с# же можно большой объект разместить в стеке (например, создать структуру с множеством полей). получается , в с++ и с# разные стеки?? и почему 1 мбит? в большой программе его может не хватить даже для локальных переменных. и, то есть, стек выделяется для каждого процесса свой личный, а куча для всех процесов общая (куча — вся остальная память, не занятая стеком никакой программы)?

Возникает два вопроса: что подразумевается под термином «выделение памяти» (или «предоставление памяти») и что произойдет, если диспетчер памяти не сможет выделить блок требуемого размера?

тот же самый вопрос, если не в куче, а в стеке не хватит памяти (там же только 1 мб), то что?

Из того, что выделение памяти в стеке возлагается на компилятор, следует, что размер (а значит и тип) размещаемых в стеке объектов должен быть известен на этапе компиляции, что часто бывает невозможно. Простейшая задача обработки массива, размерность и элементы которого вводятся с клавиатуры или из файла, является ярким тому подтверждением.

когда мы читаем пользовательский ввод, мы же его присваиваем заранее созданному массиву с заданной длиной?

Надо обратить внимание на то, что при удалении адресуемого указателем p1 объекта с самим указателем p1, с его значением ничего не происходит. Оператор delete освобождает память в куче, указатель же p1 располагается в стеке, отведённая под него память будет освобождена только при выходе p1 из области видимости. В ячейках памяти, занимаемых указателем p1 в стеке (см. рис. 17), после выполнения оператора delete будет записана та же информация, что и до вызова этого оператора — число 0xF830 — адрес уже несуществующего объекта.
Таким образом, после выполнения оператора delete мы получаем недействительный указатель (dangling pointer), указатель, который адресует несуществующий объект. Разыменование такого указателя и последующий доступ к объекту в лучшем случае приведёт к немедленному аварийному завершению работы программы, в худшем — к её нестабильному поведению, которое не повторяется от одного запуска программы к другому. На выявление подобных ошибок в реальных программах зачастую уходит много времени и сил. Поэтому в тех случаях, когда планируется дальнейшая работа с указателем на удаляемый объект, имеет смысл сразу после удаления объекта присвоить указателю на него значение NULL, тем самым явно идентифицировав этот указатель как ни на что не указывающий.

А почему компилятор автоматически не присваивает ссылке NULL при операции delete?

Поэкспериментируйте с программой: попробуйте два раза подряд вызвать оператор удаления для одного и того же указателя или разыменовать нулевой указатель, посмотрите на поведение программы в каждом из случаев.

будет аварийное завершение, потому что диспетчер памяти посчитает, что мы обращаемся к памяти, которую никто не занимает и не даст прочитать с нее еще не стертые другим процессом данные?
Re: То, что не описано в статье, но всех интересует
От: DarkTranquillity  
Дата: 05.04.12 10:34
Оценка: 4 (1)
Здравствуйте, help-me, Вы писали:

HM>

HM>Операция разыменования становится возможной исключительно в силу типизации указателей: в типе заложена информация о размере памяти, необходимой для размещения объекта, и о способе интерпретации содержащихся в ней данных, что в совокупности с адресом однозначно идентифицирует объект в памяти.

HM>Получается, любая переменная (или указатель на переменную) кроме адреса должен хранить и тип(размер) переменной, чтобы при создании, удалении или чтении\перезаписи значения этой переменной использовать ровно столько байт, сколько есть в переменной, не больше и не меньше (но тогда на x86 размер указателя или самой переменной должен быть не 4байта(только адрес), а больше?)?

Нет, размер объекта и смещения его полей известны на этапе компиляции, поэтому для доступа к объекту достаточно знать адрес его "начала" — остальное компилятор посчитает сам.

HM>

HM>Адрес объекта является той характеристикой, которая идентифицирует объект, отличает один объект от всех других объектов в системе.

HM>В первой цитате написано, что не только адрес, но и тип(размер) однозначно идентифицируют, тогда почему во второй цитате говорится только про адрес?

Не докапывайтесь к ерунде.

HM>

HM>Кроме того, размер стека в большинстве случаев ограничен (для программ, разрабатываемых в среде Microsoft Visual Studio, размер стека по умолчанию — 1 Мб), что приводит к невозможности размещения в нём больших объектов, к примеру, тех же массивов.

HM>это только в с++ ? в с# же можно большой объект разместить в стеке (например, создать структуру с множеством полей). получается , в с++ и с# разные стеки?? и почему 1 мбит? в большой программе его может не хватить даже для локальных переменных. и, то есть, стек выделяется для каждого процесса свой личный, а куча для всех процесов общая (куча — вся остальная память, не занятая стеком никакой программы)?

Для компьютеров на платформе x86 и x64 по умолчанию выделяется стек объемом 1 МБ. Для компьютеров на платформе Itanium по умолчанию выделяется стек объемом 4 МБ.

— МСДН
Нет, С++ тут не причем. Вообще, информация о размере стека хранится в заголовке exe-файла, то есть заполняет это линкер, настраивается в опциях проекта. (компилятор вообще не при делах). Но, судя по описанию CreateThread, именно такой размер стека по умолчанию зашит в системе.
Ахринеть, это что за "большая" такая программа?
Стек выделяется свой для каждого потока, а куча — одна глобальная на процесс, плюс можно создавать свои кучи.

HM>

HM>Возникает два вопроса: что подразумевается под термином «выделение памяти» (или «предоставление памяти») и что произойдет, если диспетчер памяти не сможет выделить блок требуемого размера?

HM>тот же самый вопрос, если не в куче, а в стеке не хватит памяти (там же только 1 мб), то что?

То ОС бросит исключение SEH stack overflow.

HM>

HM>Из того, что выделение памяти в стеке возлагается на компилятор, следует, что размер (а значит и тип) размещаемых в стеке объектов должен быть известен на этапе компиляции, что часто бывает невозможно. Простейшая задача обработки массива, размерность и элементы которого вводятся с клавиатуры или из файла, является ярким тому подтверждением.

HM>когда мы читаем пользовательский ввод, мы же его присваиваем заранее созданному массиву с заданной длиной?

Вот потому и используются указатели. То, чему Вы присваиваете пользовательский ввод — Ваше дело. В современных ОС и языках есть функции, позволяющие задавать максимальное количество символов, дабы не получить переполнение буфера со всеми вытекающими.


HM>

HM>Надо обратить внимание на то, что при удалении адресуемого указателем p1 объекта с самим указателем p1, с его значением ничего не происходит. Оператор delete освобождает память в куче, указатель же p1 располагается в стеке, отведённая под него память будет освобождена только при выходе p1 из области видимости. В ячейках памяти, занимаемых указателем p1 в стеке (см. рис. 17), после выполнения оператора delete будет записана та же информация, что и до вызова этого оператора — число 0xF830 — адрес уже несуществующего объекта.
HM>Таким образом, после выполнения оператора delete мы получаем недействительный указатель (dangling pointer), указатель, который адресует несуществующий объект. Разыменование такого указателя и последующий доступ к объекту в лучшем случае приведёт к немедленному аварийному завершению работы программы, в худшем — к её нестабильному поведению, которое не повторяется от одного запуска программы к другому. На выявление подобных ошибок в реальных программах зачастую уходит много времени и сил. Поэтому в тех случаях, когда планируется дальнейшая работа с указателем на удаляемый объект, имеет смысл сразу после удаления объекта присвоить указателю на него значение NULL, тем самым явно идентифицировав этот указатель как ни на что не указывающий.

HM>А почему компилятор автоматически не присваивает ссылке NULL при операции delete?

HM>

HM>Поэкспериментируйте с программой: попробуйте два раза подряд вызвать оператор удаления для одного и того же указателя или разыменовать нулевой указатель, посмотрите на поведение программы в каждом из случаев.

HM>будет аварийное завершение, потому что диспетчер памяти посчитает, что мы обращаемся к памяти, которую никто не занимает и не даст прочитать с нее еще не стертые другим процессом данные?

Наконец-то! Вы почти угадали! Только не даст он прочитать по нулевому указателю, а другие процессы вообще не причем, потому что адресные пространства процессов никак не зависят друг от друга — виртуальный режим.
Re: То, что не описано в статье, но всех интересует
От: Pzz Россия https://github.com/alexpevzner
Дата: 05.04.12 11:22
Оценка: 1 (1)
Здравствуйте, help-me, Вы писали:

HM>это только в с++ ? в с# же можно большой объект разместить в стеке (например, создать структуру с множеством полей). получается , в с++ и с# разные стеки?? и почему 1 мбит? в большой программе его может не хватить даже для локальных переменных. и, то есть, стек выделяется для каждого процесса свой личный, а куча для всех процесов общая (куча — вся остальная память, не занятая стеком никакой программы)?


А представьте себе, код, исполняющийся в контексте ядра (например, драйвер) может рассчитывать только на 12К (двенадцать килобайт) в венде, 4К (четыре килобайта) в линухе. Причем полностью под себя он их использовать не может, потому что неизвестно откуда этот код позвали, и сколько места в стеке к этому времени уже использовали. Поэтому разработчики драйверов рано седеют и рано умирают

HM>А почему компилятор автоматически не присваивает ссылке NULL при операции delete?


Потому что считается, что C++ предназначен для написания эффективных программ. А автоматическое зануление указателя добавляет лишние команды, что не способствует эффективности.
Re[2]: То, что не описано в статье, но всех интересует
От: opener  
Дата: 05.04.12 13:30
Оценка: 1 (1)
Здравствуйте, Pzz, Вы писали:

Pzz>А представьте себе, код, исполняющийся в контексте ядра (например, драйвер) может рассчитывать только на 12К (двенадцать килобайт) в венде, 4К (четыре килобайта) в линухе.


4 кбайта это очень много. Я в четыре килобайта ПЗУ + 128 байт ОЗУ (какой-то мелкий AVR Atmel) запихивал считывание данных с АЦП, пересчет их в температуру с точностью 0.1 градуса с аппроксимацией нелинейности термопары полиномом, калибровку, протокол USART и проверку целостности собственной прошивки и параметров настройки с помощью CRC32. Попривыкали тут, блин, писать на всяком гавне типа джавы.

HM>>А почему компилятор автоматически не присваивает ссылке NULL при операции delete?


А нафига?

Pzz>Потому что считается, что C++ предназначен для написания эффективных программ. А автоматическое зануление указателя добавляет лишние команды, что не способствует эффективности.


Что значит "считается"? Он именно для этого и предназначен. Сочетает мощь и удобство высокоуровневых языков и эффективность низкоуровневых (таких как С или Ассемблер).
Re: То, что не описано в статье, но всех интересует
От: Vamp Россия  
Дата: 05.04.12 17:49
Оценка: 4 (1)
HM>Операция разыменования становится возможной исключительно в силу типизации указателей: в типе заложена информация о размере памяти, необходимой для размещения объекта, и о способе интерпретации содержащихся в ней данных, что в совокупности с адресом однозначно идентифицирует объект в памяти.

HM>Получается, любая переменная (или указатель на переменную) кроме адреса должен хранить и тип(размер) переменной, чтобы при создании, удалении или чтении\перезаписи значения этой переменной использовать ровно столько байт, сколько есть в переменной, не больше и не меньше (но тогда на x86 размер указателя или самой переменной должен быть не 4байта(только адрес), а больше?)?'

Следует отличать информацию, известную компилятору во время компиляции от информации, доступной программе во время исполнения. Компилятор ВСЕГДА знает [статический] тип переменной. А на этапе выполнения эта информация, как правило, недоступна — все, что нам известно, это адрес переменной и ее размер.
Сам по себе указатель имеет размер, необходимый для хранения информации об адресуемой области (не обязательно 4 байта, кстати). Но выделенная динамически область памяти имеет специальный блок, который хранит информацию о размере этой области. Однако, с точки зрения программы эта информация недоступна и не нужна. Она нужна только менеджеру памяти.

HM>Адрес объекта является той характеристикой, которая идентифицирует объект, отличает один объект от всех других объектов в системе.

HM>В первой цитате написано, что не только адрес, но и тип(размер) однозначно идентифицируют, тогда почему во второй цитате говорится только про адрес?
См. выше.

HM>

HM>Кроме того, размер стека в большинстве случаев ограничен (для программ, разрабатываемых в среде Microsoft Visual Studio, размер стека по умолчанию — 1 Мб), что приводит к невозможности размещения в нём больших объектов, к примеру, тех же массивов.

HM>это только в с++ ? в с# же можно большой объект разместить в стеке (например, создать структуру с множеством полей).
Я не большой знаток С#, но почему вы думаете, что эта структура будет размещена в стеке? Скорее всего, в куче.

HM>и, то есть, стек выделяется для каждого процесса свой личный, а куча для всех процесов общая (куча — вся остальная память, не занятая стеком никакой программы)?

Куча тоже не общая. У каждого процесса свое собственное адресное пространство (вопрос "общей памяти" и MMF оставим за скобками).


HM>

HM>Возникает два вопроса: что подразумевается под термином «выделение памяти» (или «предоставление памяти») и что произойдет, если диспетчер памяти не сможет выделить блок требуемого размера?

Выделение памяти — это процесс, посредством которого в адресном пространстве процесса появляется новая адресуемая память. Образно говоря, изначально адресное пространство процесса — это такой большой океан без кусочка суши. Места вроде много, а присесть некуда. Выделение памяти создает "остров" суши в этом океане — теперь на нем можно, например, построить дом (создать объект). Если в куче не хватит памяти (не будет "земли" чтобы насыпать новый остров) то менеджер памяти вернет код ошибки. В зависимости от использованных конструкций С/С++ этот код ошибки может быть также возвращен программе или сгенерировано исключение.

HM>тот же самый вопрос, если не в куче, а в стеке не хватит памяти (там же только 1 мб), то что?

Аварийное завершение программы.

HM>

HM>Из того, что выделение памяти в стеке возлагается на компилятор, следует, что размер (а значит и тип) размещаемых в стеке объектов должен быть известен на этапе компиляции, что часто бывает невозможно. Простейшая задача обработки массива, размерность и элементы которого вводятся с клавиатуры или из файла, является ярким тому подтверждением.

HM>когда мы читаем пользовательский ввод, мы же его присваиваем заранее созданному массиву с заданной длиной?
Данное высказывание несколько устарело. Например, С99 разрешает массивы с длинной, неизвестной на этапе компиляции.

HM>

HM>Надо обратить внимание на то, что при удалении адресуемого указателем p1 объекта с самим указателем p1, с его значением ничего не происходит. Оператор delete освобождает память в куче, указатель же p1 располагается в стеке, отведённая под него память будет освобождена только при выходе p1 из области видимости. В ячейках памяти, занимаемых указателем p1 в стеке (см. рис. 17), после выполнения оператора delete будет записана та же информация, что и до вызова этого оператора — число 0xF830 — адрес уже несуществующего объекта.

HM>А почему компилятор автоматически не присваивает ссылке NULL при операции delete?
Потому, что количество указателей, ссылающихся на данный объект, компилятору неизвестно. Можно было бы занулить этот конкретный, но что делать со всеми (неизвестными) остальными?

HM>

HM>Поэкспериментируйте с программой: попробуйте два раза подряд вызвать оператор удаления для одного и того же указателя или разыменовать нулевой указатель, посмотрите на поведение программы в каждом из случаев.

HM>будет аварийное завершение, потому что диспетчер памяти посчитает, что мы обращаемся к памяти, которую никто не занимает и не даст прочитать с нее еще не стертые другим процессом данные?
Поэкспериментируйте!
Да здравствует мыло душистое и веревка пушистая.
Re: То, что не описано в статье, но всех интересует
От: PanychY  
Дата: 09.04.12 15:57
Оценка:
Здравствуйте, help-me, Вы писали:

HM>А почему компилятор автоматически не присваивает ссылке NULL при операции delete?


Потому что читать нужно стандарты и умные книжки, ну в крайнем случае преподов слушаться, а не всякую ахинею.
Потому-что оператор delete/delete[] поулчает rvalue и следовательно ничего за-NULL-ить не может.
Я удивляюсь, почему никто не спросил ещё "почему функция free не за-NULL-яет указатель). Или уже спросили?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.