напомнила мне о следующем.
E>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива.
АТ>Не понимаю вопроса. Мы действительно не можем знать размер массива. А компилятор — может. Деструкторы-то вызывает компилятор, а не мы.
Логика: мы не можем ..., а компилятор — может, меня и смутила.
Вопрос произошел от указанной темы об определении размера массива, поэтому и стало интересно, кто чего может. То есть я перефразировал бы свою мысль следующим образом: почему компилятор имеет средства для определения размера массива (то есть количества элементов), а программа таких стандартных средств не имеет.
Буду очень признателен, если вы ответите на этот вопрос.
Евгений Флоров.
Евгений Флоров
Нельзя определить размер динам. массива, хотя delete[] есть
E>Логика: мы не можем ..., а компилятор — может, меня и смутила. E>Вопрос произошел от указанной темы об определении размера массива, поэтому и стало интересно, кто чего может. То есть я перефразировал бы свою мысль следующим образом: почему компилятор имеет средства для определения размера массива (то есть количества элементов), а программа таких стандартных средств не имеет.
Я совсем недавно отвечал на точно такой же вопрос в comp.lang.c++:
Компилятор совсем не обязательно имеет средства для определения размера массива, выделенного в динамической памяти. Он компилятора требуется только то, чтобы он умел правильно выделять такие массивы при помощи 'new[]' и правильно уничтожать такие массивы при помощи 'delete[]'. Как компилятор это делает и какая дополнительная информация понадобится для этого компилятору и понадобится ли она вообще — личное дело компилятора. Классическим примером такой дополнительной информации является количество сконструированных элементов в массиве, которое нужно, например, для того, чтобы правильно выполнить деструкцию элементов массива. Если компилятору эта информация нужна только для того и только для того, чтобы вызвать правильное количество деструкторов, то сразу приходит на ум очевидная оптимизация: если хранимые в массиве объекты не имеют деструкторов (т.е. не являются экземплярами классов) или имеют тривиальные деструкторы (т.е. фактически тоже не имеют деструкторов), то знать количество элементов в массиве компилятору совершенно незачем, и формировать и хранить это количество нигде не надо. Эту оптимизацию используют многие компиляторы, включая MSVC и GCC.
Если бы существовали стандартные средства для определения размера динамически выделенного массива через указатель на его первый элемент, то компиляторам пришлось бы сохранять информацию о размере всегда. Вышеописанная оптимизация использования памяти была бы невозможна. Это во-первых.
Во-вторых, для единообразия, пришлось бы поддержать эту функциональность и для статических и автоматических массивов, что тоже несколько противречит общему духу встроенных С/С++ массивов.
Все это, на мой взгляд, выглядит совершенно не нужно. Особенно если учесть, что пользователь при выделении массива держал в руках его размер и мог сам позаботиться о сохранении этого размера для дальнейшего использования.
напомнила мне о следующем.
E>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>>то есть
E>>
E>>...
E>>int i = 100;
E>>foo *ptr = new foo[i];
E>>...
E>>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>>
E>>Евгений Флоров
Gi>кол-во элементов = размер выделенной памяти/sizeof(foo), и то и другое компилеру известно.
Как раз ему это в общем случае при использовании new неизвестно.
Например, i может быть введено пользователем в runtime.
А размер выделенной памяти ему не известен, потому что компилятор не занимается выделением памяти,
а вызывает operator new, который, в свою очередь, в общем случае дергает операционную систему,
а сколько она выделит памяти, никто не знает.
Ему все это известно только для статических массивов. (А если неизвестно, то программаа просто не скомпилируется.)
Здравствуйте Kaa, Вы писали:
Kaa>Здравствуйте epflorov, Вы писали:
E>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>>то есть
E>>
E>>...
E>>int i = 100;
E>>foo *ptr = new foo[i];
E>>...
E>>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>>
Kaa>Проблема то вся в том и есть, что мы — не компилятор. Мы не знаем, а он знает. А хранит он это в некотором заголовке к выделенной памяти, т.е. выделяется на самом деле не n байт, а n + размер заголовка, и туда складывается инфа о таких вещах, как размер выделяемого блока, и, возможно, еще что-то. Таким образом, образуется некоторый overhead, зато информация присутствует и доступна в любой момент.
Компилятор ничего не знает, кроме sizeof(foo) и того, что память, освобождаемая при помощи delete[], (должна быть) выделена динамически.
Пример:
int i;
cout << "enter i:";
cin >> i;
foo *ptr = new foo[i];
...
delete[] ptr;
Не надо так кричать. Обычно стандартная реализация new выделяет немного больше памяти, и в это немного больше помещается дополнительная информация (возможно, это самое i, возможно, i*sizeof(foo), это не важно), и потом, в рантайме, проверяет это значение, в котором хранится эта доп. информация.
Ты наверное имеешь ввиду, что это самое число ему, компилятору, не известно, т.к. к моменту, когда все произойдет, компилятор уже отработает и будет выкинут на свалку? Это да. Но он знает (в момент компиляции, а не в момент выполнения программы), где это число искать. Так-что, знает.
j>Компилятор ничего не знает!!!
Здравствуйте jazzer, Вы писали:
Gi>>кол-во элементов = размер выделенной памяти/sizeof(foo), и то и другое компилеру известно.
J>Как раз ему это в общем случае при использовании new неизвестно. J>Например, i может быть введено пользователем в runtime. J>А размер выделенной памяти ему не известен, потому что компилятор не занимается выделением памяти, J>а вызывает operator new, который, в свою очередь, в общем случае дергает операционную систему, J>а сколько она выделит памяти, никто не знает.
ИМХО, в runtime код выполнения вообще не знает о том, что он делает, что он выделяет память: процессор просто выполняет инструкции, и что в этот момент делается ему по барабану. Во время компиляции компилятор тоже не знает о том, что он выделяет память — он просто транслирует некий языковый код.
Так, кто же знает, что при new A[size] происходит выделение определённого количества памяти?
Скорее всего, тот, кто писал код компилятора. Именно он составил свой код так, чтобы он размещал в стеке или ещё где всю необходимую информацию при обработке языкового выражения new A[size] и соответственно что-то вызывал.
ИТОГО: В предыдущем ответе использован слэнг программистов.
Да, именно компилятор знает количество экземпляров инстантиируемых объектов, через константу или через целочисленное выражение в скобках [], которое он и передаёт как параметр вызываемому методу new_xxxx (или использует другой механизм).
J>Ему все это известно только для статических массивов. (А если неизвестно, то программаа просто не скомпилируется.)
Здравствуйте Vi2, Вы писали:
Vi2>Здравствуйте jazzer, Вы писали:
Gi>>>кол-во элементов = размер выделенной памяти/sizeof(foo), и то и другое компилеру известно.
J>>Как раз ему это в общем случае при использовании new неизвестно. J>>Например, i может быть введено пользователем в runtime. J>>А размер выделенной памяти ему не известен, потому что компилятор не занимается выделением памяти, J>>а вызывает operator new, который, в свою очередь, в общем случае дергает операционную систему, J>>а сколько она выделит памяти, никто не знает.
Vi2>ИМХО, в runtime код выполнения вообще не знает о том, что он делает, что он выделяет память: процессор просто выполняет инструкции, и что в этот момент делается ему по барабану. Vi2>Во время компиляции компилятор тоже не знает о том, что он выделяет память — он просто транслирует некий языковый код.
Vi2>Так, кто же знает, что при new A[size] происходит выделение определённого количества памяти?
Vi2>Скорее всего, тот, кто писал код компилятора. Именно он составил свой код так, чтобы он размещал в стеке или ещё где всю необходимую информацию при обработке языкового выражения new A[size] и соответственно что-то вызывал.
Vi2>ИТОГО: В предыдущем ответе использован слэнг программистов.
Vi2>Да, именно компилятор знает количество экземпляров инстантиируемых объектов, через константу или через целочисленное выражение в скобках [], которое он и передаёт как параметр вызываемому методу new_xxxx (или использует другой механизм).
В том-то и дело, что он не знает количество экземпляров инстантиируемых объектов, и никто не обязан давать ему в new именно константу. Но зато он "знает, где искать" (с) Эйнштейн.
И вообще, как мы недавно выяснили с Каа, компилятор ничего не знает
J>>Ему все это известно только для статических массивов. (А если неизвестно, то программаа просто не скомпилируется.)
Здравствуйте jazzer, Вы писали:
J>В том-то и дело, что он не знает количество экземпляров инстантиируемых объектов, и никто не обязан давать ему в new именно константу. Но зато он "знает, где искать" (с) Эйнштейн.
"никто не обязан давать" — сильно сказано! В том-то и дело, что тот, кто пишет код new A[size] и указывает вполне определённое выражение для size, и даёт компилятору знание. И неважно, что это не константа — компилятор-то сам не выделяет память, а инструктирует runtime-код о такой операции.
А если он (кто пишет код) ничего не укажет (раз не обязан!), то и получит ошибку компиляции. Всё просто, как в танке.
Здравствуйте epflorov, Вы писали:
Kaa>>Предлагаю темой недели вынести. E>Это как?
Ну, не знаю. Творчески к этому подойти.
Вот вариант например: всю неделю отвечать на всевозможные вопросы, касающиеся работы с памятью во всех форумах вне зависимости от того, спрашивал кто-то, или нет.
Более жестко — о чем бы человек не спрашивал, отвечать ему о распределении памяти (ну, это я перегнул, я ж говорил о творчестве)
Здравствуйте jazzer, Вы писали:
J>Здравствуйте Vi2, Вы писали:
Vi2>>Здравствуйте jazzer, Вы писали:
Gi>>>>кол-во элементов = размер выделенной памяти/sizeof(foo), и то и другое компилеру известно.
J>>>Как раз ему это в общем случае при использовании new неизвестно. J>>>Например, i может быть введено пользователем в runtime. J>>>А размер выделенной памяти ему не известен, потому что компилятор не занимается выделением памяти, J>>>а вызывает operator new, который, в свою очередь, в общем случае дергает операционную систему, J>>>а сколько она выделит памяти, никто не знает.
Vi2>>ИМХО, в runtime код выполнения вообще не знает о том, что он делает, что он выделяет память: процессор просто выполняет инструкции, и что в этот момент делается ему по барабану. Vi2>>Во время компиляции компилятор тоже не знает о том, что он выделяет память — он просто транслирует некий языковый код.
Vi2>>Так, кто же знает, что при new A[size] происходит выделение определённого количества памяти?
Vi2>>Скорее всего, тот, кто писал код компилятора. Именно он составил свой код так, чтобы он размещал в стеке или ещё где всю необходимую информацию при обработке языкового выражения new A[size] и соответственно что-то вызывал.
Vi2>>ИТОГО: В предыдущем ответе использован слэнг программистов.
Vi2>>Да, именно компилятор знает количество экземпляров инстантиируемых объектов, через константу или через целочисленное выражение в скобках [], которое он и передаёт как параметр вызываемому методу new_xxxx (или использует другой механизм).
J>В том-то и дело, что он не знает количество экземпляров инстантиируемых объектов, и никто не обязан давать ему в new именно константу. Но зато он "знает, где искать" (с) Эйнштейн.
Не буду многословен, тем более что всё уже написано в соседней ветке, посмотри ответ Андрея Тарасевича, там где "ещё раз и по-русски".
Здравствуйте jazzer, Вы писали:
J>Компилятор ничего не знает, кроме sizeof(foo) и того, что память, освобождаемая при помощи delete[], (должна быть) выделена динамически. J>Пример:
int i;
J>cout << "enter i:";
J>cin >> i;
J>foo *ptr = new foo[i];
J>...
J>delete[] ptr;
Зато компилятор (как велит ему кодировщик или разработчик компилятора) знает, что по адресу &i лежит число, которое он должен передать оператору new[] для его корректной работы.
А само это число компилятор действительно не знает, да и не зачем ему оно ему?!
Здравствуйте Vi2, Вы писали:
Vi2>Здравствуйте jazzer, Вы писали:
J>>В том-то и дело, что он не знает количество экземпляров инстантиируемых объектов, и никто не обязан давать ему в new именно константу. Но зато он "знает, где искать" (с) Эйнштейн.
Vi2>"никто не обязан давать" — сильно сказано! В том-то и дело, что тот, кто пишет код new A[size] и указывает вполне определённое выражение для size, и даёт компилятору знание. И неважно, что это не константа — компилятор-то сам не выделяет память, а инструктирует runtime-код о такой операции.
Vi2>А если он (кто пишет код) ничего не укажет (раз не обязан!), то и получит ошибку компиляции. Всё просто, как в танке.
Вы передергиваете или невнимательны.
Я написал: "никто не обязан давать ему в new именно константу".
Я не писал: "никто не обязан ему давать что-либо".
Здравствуйте Kaa, Вы писали:
Kaa>Здравствуйте epflorov, Вы писали:
Kaa>>>Предлагаю темой недели вынести. E>>Это как?
Kaa>Ну, не знаю. Творчески к этому подойти.
Kaa>Вот вариант например: всю неделю отвечать на всевозможные вопросы, касающиеся работы с памятью во всех форумах вне зависимости от того, спрашивал кто-то, или нет.
Kaa>Более жестко — о чем бы человек не спрашивал, отвечать ему о распределении памяти (ну, это я перегнул, я ж говорил о творчестве)
В этом ключе отвечать можно так: "Object is a region of storage"
Здравствуйте jazzer, Вы писали:
J>Вы передергиваете или невнимательны. J>Я написал: "никто не обязан давать ему в new именно константу". J>Я не писал: "никто не обязан ему давать что-либо".
Каюсь, виноват. Передёрнул по невнимательности.
А вообще тема интересная, как при постройке Собора Парижской Богоматери — чернорабочий просто ямы роет, каменщик просто камни кладёт и т.д.. А в итоге — Собор построен.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ> Если компилятору эта информация нужна только для того и только для того, чтобы вызвать правильное количество деструкторов, то сразу приходит на ум очевидная оптимизация: если хранимые в массиве объекты не имеют деструкторов (т.е. не являются экземплярами классов) или имеют тривиальные деструкторы (т.е. фактически тоже не имеют деструкторов), то знать количество элементов в массиве компилятору совершенно незачем, и формировать и хранить это количество нигде не надо. Эту оптимизацию используют многие компиляторы, включая MSVC и GCC.
Попробую с вами не согласиться.
Во первых вы тут сами себе противоречите — а если объекты имеют нетривиальные деструктора? Тогда размер нужен обязательно, в этом случае вы предлагаете хранить bool нужен размер / не нужен размер? Такая оптимизация не имеет смысла.
А во вторых, не верите мне, поверьте Александреску. "Современное проектирование на C++", №4.7. Там утверждается что компилятор в любом случае имеет инфу о размере удаляемого блока и более того, её можно получить с помощью специальной перегрузки оператора:
void operator delete(void* p, std::size_t size);
Re[2]: Нельзя определить размер динам. массива, хотя delete[
Здравствуйте, Яшин Евгений, Вы писали:
ЯЕ>Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>> Если компилятору эта информация нужна только для того и только для того, чтобы вызвать правильное количество деструкторов, то сразу приходит на ум очевидная оптимизация: если хранимые в массиве объекты не имеют деструкторов (т.е. не являются экземплярами классов) или имеют тривиальные деструкторы (т.е. фактически тоже не имеют деструкторов), то знать количество элементов в массиве компилятору совершенно незачем, и формировать и хранить это количество нигде не надо. Эту оптимизацию используют многие компиляторы, включая MSVC и GCC.
ЯЕ>Попробую с вами не согласиться. ЯЕ> Во первых вы тут сами себе противоречите — а если объекты имеют нетривиальные деструктора? Тогда размер нужен обязательно,
Совершенно верно. В этом случае размер нужен обязательно. И компилятор его хранит — именно в этом случае.
ЯЕ> в этом случае вы предлагаете хранить bool нужен размер / не нужен размер? Такая оптимизация не имеет смысла.
Ничего подобного я не предлагаю. Факт наличия нетривиального деструктора однозначно определяется статическим типом выражения, использованного в качестве аргумента 'delete[]', т.е. известен на стадии компиляции. На основе этой инофрмации компилятор просто генерирует правильную реализацию 'delete[]': либо "нетривиальную", читающую размер и вызывающую деструкторы перед освобождением памяти, либо "тривиальную", просто сразу освобождающую память.
ЯЕ> А во вторых, не верите мне, поверьте Александреску. "Современное проектирование на C++", №4.7. Там утверждается что компилятор в любом случае имеет инфу о размере удаляемого блока и более того, её можно получить с помощью специальной перегрузки оператора:
ЯЕ>
Есть доступный RTL размер выделенного блока сырой памяти в байтах. Это — размер "низкого уровня" — прямой аналог того самого размера, который используется еще сишной функцией 'free()' для блока памяти, выделенного 'malloc' (а если честно — это и есть тот самый размер, унаследованный еще из С). Именно этот размер и передается в 'operator new' и 'operator delete'.
И есть размер выделенного массива в элементах. Это отдельный самостоятельный размер, который, для массивов элементов с нетривиальным деструктором, отдельно и самостоятельно хранится. Зачастую можно услышать, что этот второй размер можно получить из первого путем деления первого размера на sizeof элемента. Это в общем случае не верно. Функции выделения сырой памяти RTL имеют права по своему усмотрению выделять больше памяти, чем у них просили. Поэтому результат такого деления может оказаться неверным (большим, чем должно было бы быть). Поэтому размер массива в элементах хранится отдельно, дополнительно к размеру блока сырой памяти в байтах.
Таким образом, выделяя память через 'new int[8]', мы просим и получаем от RTL блок памяти в '8 * sizeof(int)' байтов или несколько больше и RTL где-то запоминает размер этого блока. Обычно это делается в самом блоке, "слева" от возвращенного указателя, т.е. фактически размер выделенного блока будет еще, скажем, на 4 байта больше (будем считать что наша платформа использует 4 байта для хранения размера блоков сырой памяти). Запоминать же размер массива в элементах в этом случае не надо, т.к. тип 'int' не требует деструктирования.
Выделяя же память через 'new C[8]' (где C — класс с нетривиальным деструктором) в традиционной реализации мы (с точки зрения 'operator new') просим у RTL блок памяти длиной в '8 * sizeof(C) + 4' байт. Эта подсистема даст нам такой блок (или больше) не забыв запомнить его размер в байтах "слева" от возвращенного указателя. Реализация 'new[]' же использует первые 4 байта этого блока для запоминания размера массива, т.е. запишет туда 8, сконструирует в остальной памяти 8 элементов и вернет нам указатель на первый из них. Т.е. в этом случае "слева" от возвращенного указателя хранится два размера — размер массива в элементах и размер блока памяти в байтах.
Я в своем сообщении говорил именно об опциональности хранения второго размера (размера массива в элементах). А первый размер хранится всегда. Все это Александреску никак не противоречит.
Best regards,
Андрей Тарасевич
Re[3]: Нельзя определить размер динам. массива, хотя delete[
АТ>Таким образом, выделяя память через 'new int[8]', мы просим и получаем от RTL блок памяти в '8 * sizeof(int)' байтов или несколько больше и RTL где-то запоминает размер этого блока. Обычно это делается в самом блоке, "слева" от возвращенного указателя, т.е. фактически размер выделенного блока будет еще, скажем, на 4 байта больше (будем считать что наша платформа использует 4 байта для хранения размера блоков сырой памяти). Запоминать же размер массива в элементах в этом случае не надо, т.к. тип 'int' не требует деструктирования.
АТ>Выделяя же память через 'new C[8]' (где C — класс с нетривиальным деструктором) в традиционной реализации мы (с точки зрения 'operator new') просим у RTL блок памяти длиной в '8 * sizeof(C) + 4' байт. Эта подсистема даст нам такой блок (или больше) не забыв запомнить его размер в байтах "слева" от возвращенного указателя. Реализация 'new[]' же использует первые 4 байта этого блока для запоминания размера массива, т.е. запишет туда 8, сконструирует в остальной памяти 8 элементов и вернет нам указатель на первый из них. Т.е. в этом случае "слева" от возвращенного указателя хранится два размера — размер массива в элементах и размер блока памяти в байтах.
Хорошо, т.е. вы хотите сказать, что вот в таком коде
class C
{
int v;
~C(){}
};
void f()
{
C* ptr = new C[100];
delete[] (int*)ptr;
}
компилятор просто не сможет правильно вычислить адрес начала "сырой" выделеной памяти и, попросту говоря, всё хлопнеться?
А вы сможете подтвердить такое поведение пунктом стандарта?