напомнила мне о следующем.
E>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>то есть
E>
E>...
E>int i = 100;
E>foo *ptr = new foo[i];
E>...
E>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>
Читаем учебники (т.е. Страуструпа)
"Стандартная реализация new выделяет памяти немного больше, чем потребовалось бы для статического объекта. Как правило, используется одно дополнительное слово для хранения размера объекта".
напомнила мне о следующем.
E>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>то есть
E>
E>...
E>int i = 100;
E>foo *ptr = new foo[i];
E>...
E>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>
E>Евгений Флоров
кол-во элементов = размер выделенной памяти/sizeof(foo), и то и другое компилеру известно.
Здравствуйте epflorov, Вы писали:
E>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>то есть
E>
E>...
E>int i = 100;
E>foo *ptr = new foo[i];
E>...
E>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>
Проблема то вся в том и есть, что мы — не компилятор. Мы не знаем, а он знает. А хранит он это в некотором заголовке к выделенной памяти, т.е. выделяется на самом деле не n байт, а n + размер заголовка, и туда складывается инфа о таких вещах, как размер выделяемого блока, и, возможно, еще что-то. Таким образом, образуется некоторый overhead, зато информация присутствует и доступна в любой момент.
напомнила мне о следующем.
E>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>то есть
E>
E>...
E>int i = 100;
E>foo *ptr = new foo[i];
E>...
E>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>
Ниоткуда...
В данном случае было бы неплохо сделать так:
E>...
E>int i = 100;
E>foo *ptr = new foo[i];
E>...
for(int j=0; j<i; j++)
delete(ptr[j])
напомнила мне о следующем.
E>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>>то есть
E>>
E>>...
E>>int i = 100;
E>>foo *ptr = new foo[i];
E>>...
E>>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>>
КД>Ниоткуда...
КД>В данном случае было бы неплохо сделать так:
КД>
E>>...
E>>int i = 100;
E>>foo *ptr = new foo[i];
E>>...
КД>for(int j=0; j<i; j++)
КД>delete(ptr[j])
КД>
Хммм... Рискну не согласиться.
Насколько я знаю, вполне корректный код. Т.е. деструкторы вызовутся для всех элементов массива. В этом и суть delete[].
напомнила мне о следующем.
E>>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>>>то есть
E>>>
E>>>...
E>>>int i = 100;
E>>>foo *ptr = new foo[i];
E>>>...
E>>>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>>>
КД>>Ниоткуда...
КД>>В данном случае было бы неплохо сделать так:
КД>>
E>>>...
E>>>int i = 100;
E>>>foo *ptr = new foo[i];
E>>>...
КД>>for(int j=0; j<i; j++)
КД>>delete(ptr[j])
КД>>
A>Хммм... Рискну не согласиться. A>Насколько я знаю, вполне корректный код. Т.е. деструкторы вызовутся для всех элементов массива. В этом и суть delete[].
Дополняя предыдущего оратора (Alik):
В этом и суть delete[]. — для встроенных типов данных... Для определенных вами типов (классов, структур), выделяемых "на куче", поведение delete[] может быть весьма непредсказыемым...
CMyClass* cPtr[20];
for(int i=0;i<20;i++)
cPtr[i] = new CMyClass;
delete [] cPtr; // так низзя :-))
Здравствуйте Flamer, Вы писали:
F>Здравствуйте Alik, Вы писали:
A>>Здравствуйте Койнов Дмитрий, Вы писали:
КД>>>Здравствуйте epflorov, Вы писали:
E>>>>Здравствуйте.
E>>>>Тема http://www.rsdn.ru/forum/message.asp?mid=72857
напомнила мне о следующем.
E>>>>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива. E>>>>то есть
E>>>>
E>>>>...
E>>>>int i = 100;
E>>>>foo *ptr = new foo[i];
E>>>>...
E>>>>delete []ptr; //откуда здесь становится известно о количестве элементов в массиве?
E>>>>
КД>>>Ниоткуда...
КД>>>В данном случае было бы неплохо сделать так:
КД>>>
E>>>>...
E>>>>int i = 100;
E>>>>foo *ptr = new foo[i];
E>>>>...
КД>>>for(int j=0; j<i; j++)
КД>>>delete(ptr[j])
КД>>>
A>>Хммм... Рискну не согласиться. A>>Насколько я знаю, вполне корректный код. Т.е. деструкторы вызовутся для всех элементов массива. В этом и суть delete[].
F>Дополняя предыдущего оратора (Alik):
F>В этом и суть delete[]. — для встроенных типов данных... Для определенных вами типов (классов, структур), выделяемых "на куче", поведение delete[] может быть весьма непредсказыемым...
F>
F>CMyClass* cPtr[20];
F>for(int i=0;i<20;i++)
F> cPtr[i] = new CMyClass;
F>delete [] cPtr; // так низзя :-))
F>
так здесь же удаляется память на стеке, причём тут delete[]?
F>В этом и суть delete[]. — для встроенных типов данных... Для определенных вами типов (классов, структур), выделяемых "на куче", поведение delete[] может быть весьма непредсказыемым...
F>
F>CMyClass* cPtr[20];
F>for(int i=0;i<20;i++)
F> cPtr[i] = new CMyClass;
F>delete [] cPtr; // так низзя :-))
F>
Поведение оператора delete [] предсказуемо для любых типов данных - для каждого элемента массива будет вызван деструктор. Исключение составляют встроенные типы, которые не имеют деструкторов в общем понимании.
В твоем примере массив содержит указатели, а указатели есть встроенный тип, так что delete [] здесь абсолютно не при чем.И код
CMyClass* cPtr = new CMyClass[20];
...
delete [] cPtr;
абсолютно корректен, т.е. для каждого элемента cPtr бкдет вызван деструктор.
Здравствуйте Flamer, Вы писали:
F>В этом и суть delete[]. — для встроенных типов данных... Для определенных вами типов (классов, структур), выделяемых "на куче", поведение delete[] может быть весьма непредсказыемым...
F>
F>CMyClass* cPtr[20];
F>for(int i=0;i<20;i++)
F> cPtr[i] = new CMyClass;
F>delete [] cPtr; // так низзя :-))
F>
Поясните пожалуйста.
Вы пытаетесь удалить массив выделенный в стеке?
Или Вы имели ввиду:
CMyClass* cPtr = new CMyClass[20];
for(int i=0;i<20;i++)
cPtr[i] = new CMyClass;
delete [] cPtr; // и здесь мы теряем все указатели?
E>>>>...
E>>>>int i = 100;
E>>>>foo *ptr = new foo[i];
E>>>>...
КД>>>for(int j=0; j<i; j++)
КД>>>delete(ptr[j])
КД>>>
M>>Не стОит так делать. Программа упадёт непременно. F>Не будет ли Вам сложно объяснить, почему?
Фрагмент
foo *ptr = new foo[i];
выделяет память для хранения i элементов массива, а не i блоков. В начале этого фрагмента лежит какая-то служебная информация о выделенном блоке, которая используется при удаления блока (например указатель на след блок и размер блока, необходимый для расчёта кол-ва элементов и соответственно число вызовов деструкторов). Если подсунуть опертору delete некий адрес из середины блока, то он скорее всего что-нибудь попортит, когда будет удалять его из списка блоков.
Вот задумался, а можно ли придумать хитрую реализацию, для которой бы приведённый фрагмент работал бы коректно?
Надо тщательно смотреть стандарты, чтобы ответить на этот вопрос, но скорее всего нет. Иначе надо запихивать в этот блок дополнительную информацию, что видится мне идеологически неправильным.
E>>>>>...
E>>>>>int i = 100;
E>>>>>foo *ptr = new foo[i];
E>>>>>...
КД>>>>for(int j=0; j<i; j++)
КД>>>>delete(ptr[j])
КД>>>>
и как правильно заметил Gi, ptr[i] — это объект, а не указатель, Я имел в виду, что delete(&ptr[j]) неправильно, потому что порушит цепочку блоков памяти.
напомнила мне о следующем.
E>Если мы не можем знать размер массива после его выделения, то как компилятор может вызвать деструкторы для каждого элемента массива.
Не понимаю вопроса. Мы действительно не можем знать размер массива. А компилятор — может. Деструкторы-то вызывает компилятор, а не мы.
Best regards,
Андрей Тарасевич
Re[5]: как работает delete []ptr?
От:
Аноним
Дата:
15.07.02 15:59
Оценка:
Здравствуйте Mish, Вы писали:
E>>>>>...
E>>>>>int i = 100;
E>>>>>foo *ptr = new foo[i];
E>>>>>...
КД>>>>for(int j=0; j<i; j++)
КД>>>>delete(ptr[j])
M>Вот задумался, а можно ли придумать хитрую реализацию, для которой бы приведённый фрагмент работал бы коректно?
Конечно, можно. Для этого достаточно перегрузить для класса 'foo' оператор 'new[]', оператор приведения к указателю на какой-либо тип и для этого типа оператор 'delete'. И еще — поставить ';' после 'delete(ptr[j])'.
напомнила мне о следующем.
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;
}
компилятор просто не сможет правильно вычислить адрес начала "сырой" выделеной памяти и, попросту говоря, всё хлопнеться?
А вы сможете подтвердить такое поведение пунктом стандарта?
Re[4]: Нельзя определить размер динам. массива, хотя delete[
ЯЕ>Хорошо, т.е. вы хотите сказать, что вот в таком коде
ЯЕ>
ЯЕ>class C
ЯЕ>{
ЯЕ> int v;
ЯЕ> ~C(){}
ЯЕ>};
ЯЕ>void f()
ЯЕ>{
ЯЕ> C* ptr = new C[100];
ЯЕ> delete[] (int*)ptr;
ЯЕ>}
ЯЕ>
ЯЕ>компилятор просто не сможет правильно вычислить адрес начала "сырой" выделеной памяти и, попросту говоря, всё хлопнеться?
VC7.1 — AV.
ЯЕ>А вы сможете подтвердить такое поведение пунктом стандарта?
Да хоть вот это:
5.3.5/3
...In the second alternative (delete array) if the dynamic type of the
object to be deleted differs from its static type, the behavior is undefined
Динамический тип операнда — C*, а статический — int* — имеем неопределенное поведение.
ЗЫ
Быть может будет интересно еще кое-что о delete []
Здравствуйте, Яшин Евгений, Вы писали:
ЯЕ>Хорошо, т.е. вы хотите сказать, что вот в таком коде
ЯЕ>
ЯЕ>class C
ЯЕ>{
ЯЕ> int v;
ЯЕ> ~C(){}
ЯЕ>};
ЯЕ>void f()
ЯЕ>{
ЯЕ> C* ptr = new C[100];
ЯЕ> delete[] (int*)ptr;
ЯЕ>}
ЯЕ>
ЯЕ>компилятор просто не сможет правильно вычислить адрес начала "сырой" выделеной памяти и, попросту говоря, всё хлопнеться?
Да, компилятор, который не хранит второй размер для типа 'int', но хранит для типа 'C', действительно не сможет правильно вычислить этот адрес в данном случае со всеми вытекающими.
ЯЕ>А вы сможете подтвердить такое поведение пунктом стандарта?
5.3.5/3: Если динамический тип объекта, указуемого аргументом 'delete[]', отличается от его статческого типа — поведение не определено.