освободить/удалить объединение malloc/новый массив в C/C++

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

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

typedef union {
    char* array_c;
    float* array_f;
    int* array_i;
} my_array;

. . . а затем вы выделяете один из массивов и пытаетесь удалить его откуда-то еще:

my_array arr;
arr.array_f = (float*)(malloc(10*sizeof(float)));
free(arr.array_i);

Я предполагаю, что это сработает, хотя технически это не определено из-за того, как реализована функция malloc. Я также предполагаю, что это будет работать при распределении массива_c, хотя, в отличие от int и float, массивы вряд ли будут одинакового размера.

Тест может быть повторен с новыми и удаленными, которые аналогичны. Я предполагаю, что они также будут работать.

Я предполагаю, что спецификация языка ненавидит меня за это, но я ожидаю, что это сработает. Это напоминает мне о бизнесе «не удалять новый указатель, приведенный к void*, даже если это массив, а не объект».

Итак, вопросы: что говорит спецификация об этом? Я проверил кратко, но не смог найти ничего, что касалось бы именно этого случая. В любом случае, насколько это опрометчиво — с функциональной точки зрения (я понимаю, что это ужасно с точки зрения ясности).

Это чисто любопытный вопрос для педантичных целей.


person imallett    schedule 29.06.2012    source источник
comment
маловероятно, что массивы будут одного размера - это не имеет значения, поскольку размер не передается в free; он вычисляет это из информации, хранящейся на арене. Все, что имеет значение, это то, совпадает ли значение указателя, переданного в free, со значением указателя, возвращаемым malloc... и стандарт не гарантирует этого.   -  person Jim Balter    schedule 29.06.2012
comment
В яблочко. Вот почему я предположил, что это сработает.   -  person imallett    schedule 03.08.2013
comment
Вы предполагали, что это сработает, потому что стандарт не гарантирует, что это сработает? Я предлагаю вам внимательно прочитать ответы и комментарии на этой странице. Если вы педантичны только в интересах, то педантичный ответ заключается в том, что это неопределенное поведение.   -  person Jim Balter    schedule 05.08.2013
comment
Нет; Я предполагал, что это сработает, хотя стандарт не гарантирует, что это сработает. В ответе Дэвида Шварца описаны все проблемы, поэтому я его принял.   -  person imallett    schedule 11.08.2013


Ответы (3)


Вы точно правы. Это нарушает правила:

Когда значение хранится в члене объекта типа объединения, байты представления объекта, которые не соответствуют этому члену, но соответствуют другим членам, принимают неуказанные значения, но значение объекта объединения не должно при этом становиться представление ловушки.
- стандарт ISO/IEC 9899, ​​раздел 6.2.6.1

Однако в том виде, в котором обычно выполняются реализации, он «случайно» будет работать правильно. Поскольку free принимает void *, параметр будет преобразован в void * для передачи в free. Поскольку все указатели расположены по одному и тому же адресу и все преобразования в void * не влекут за собой изменения их значения, окончательное значение, переданное в free, будет таким же, как если бы был передан правильный элемент.

Теоретически реализация может отследить, к какому члену объединения обращались последним, и испортить значение (или привести к сбою программы, или сделать что-то еще), если вы читаете член, отличный от того, который вы в последний раз записывали. Но, насколько мне известно, ни одна реальная реализация не делает ничего подобного.

person David Schwartz    schedule 29.06.2012
comment
Какое правило нарушено в данной ситуации? Мне кажется, что это не зависит от какого-либо неопределенного поведения? - person dsharlet; 29.06.2012
comment
@dsharlet Вам не разрешен доступ к члену союза, отличному от того, который вы установили. Это вызывает неопределенное поведение. - person Antimony; 29.06.2012
comment
Правило говорит о байтах за пределами устанавливаемого члена; в случае OP таких байтов нет - все указатели одного размера. Я думаю, что к его посту относится правило 6.5.2.3.5: Одна специальная гарантия делается для того, чтобы упростить использование союзов: если объединение содержит несколько структур, имеющих общую начальную последовательность (см. ниже), и если объект объединения в настоящее время содержит одну из этих структур, разрешено проверять общую начальную часть любой из них в любом месте, где видно объявление полного типа объединения. Однако я не уверен, относится ли это к указателям. - person Sergey Kalinichenko; 29.06.2012
comment
@dasblinkenlight: это относится к указателям, но не к этому случаю. Общей начальной последовательности нет. Кроме того, ваша интерпретация сделает этот раздел непонятным — что произойдет с частями, которые пересекаются? В любом случае, проблема на самом деле не столько в нарушении 6.2.6.1, сколько в том, что нет особой причины, по которой вы должны получить какой-то конкретный ответ. В реализации разрешено использовать разные форматы указателей на разные типы и разные правила их приведения к void *. - person David Schwartz; 29.06.2012
comment
@dasblinkenlight Поскольку указатели не являются структурами, 6.5.2.3.5, безусловно, не применяется. Это неопределенное поведение, как если бы эти указатели не были частью объединения — вы не можете сохранить одно значение и ожидать, что оно будет считано из другого. Ничто в стандарте не говорит, что они должны иметь такой псевдоним. - person Jim Balter; 29.06.2012
comment
В реализации разрешено использовать разные форматы для указателей - действительно. 6.2.5 явно говорит, что указатели на типы, отличные от объединений, не обязательно должны иметь одинаковые требования к представлению или выравниванию. - person Jim Balter; 29.06.2012
comment
Теоретически реализация может отслеживать, к какому члену объединения обращались последним — я полагаю, что существуют реализации интерпретаторов C, которые отслеживают, в какой член был сохранен. - person Jim Balter; 29.06.2012
comment
@JimBalter существуют реализации интерпретаторов C, которые отслеживают, в какой член был сохранен. но это не так просто, как тип варианта Pascal, потому что для объединения структур (не относящегося к этому примеру) вам разрешено осматривать общую начальную часть. Код проверки будет сложным. - person curiousguy; 18.07.2012
comment
@dasblinkenlight разрешается проверять общую начальную часть любого из них в любом месте, где видно объявление полного типа объединения. Однако я не уверен, что это применимо к указателям. Это не относится к указателям разных типов. - person curiousguy; 18.07.2012

Это неопределенное поведение, потому что вы обращаетесь к другому элементу, чем вы установили. Он может делать буквально все.

На практике это обычно работает, но на это нельзя полагаться. Компиляторы и наборы инструментов не являются преднамеренным злом, но были случаи, когда оптимизации взаимодействовали с неопределенным поведением, что приводило к совершенно неожиданным результатам. И, конечно же, если вы когда-либо будете работать в системе с другой реализацией malloc, она, вероятно, взорвется.

person Antimony    schedule 29.06.2012
comment
Он может делать буквально все — неопределенное поведение просто означает, что стандарт не предписывает, что должна делать соответствующая реализация. Реальность такова, что ваши программы всегда могут делать что угодно в пределах возможностей аппаратного обеспечения, потому что стандарт — это не физический закон. если вы когда-либо работали в системе с другой реализацией malloc — что это значит? Отличается от чего? Реализация malloc здесь неуместна; единственное, что важно, это то, являются ли элементы указателя точными псевдонимами (местоположение, представление, выравнивание) в реализации. - person Jim Balter; 29.06.2012
comment
@JimBalter: Реальность такова, что ваши программы всегда могут делать что угодно в пределах возможностей оборудования, потому что стандарт не является физическим законом. -- Это глупо. Это ответ на вопрос с тегами C и C++. Следует предположить, что в любых утверждениях о том, что разрешено, а что нет, подразумевается стандарт C (или C++). Пожалуйста, не требуйте от нас проговаривать это каждый раз. - person Benjamin Lindley; 29.06.2012
comment
Я отметил ваш комментарий и не буду исправлять ваше непонимание того, что я написал. - person Jim Balter; 29.06.2012
comment
@JimBalter: Итак, если я вас неправильно понял, что вы возражали, когда Сурьма сказал, что Он может буквально все? Потому что я понял, что у вас была проблема с тем, что он не квалифицировал это, поскольку оно может буквально делать все, что касается стандарта. -- Я ошибаюсь? - person Benjamin Lindley; 29.06.2012
comment
Стандарт налагает требования, которые должны быть выполнены, чтобы реализация считалась соответствующей. Определение неопределенного поведения – это поведение при использовании непереносимой или ошибочной программной конструкции или ошибочных данных, для которого настоящий стандарт не устанавливает требований. Говорить об этом можно буквально что угодно — это ошибка категории; он неправильно истолковывает и вводит в заблуждение относительно отношений между стандартами, реализациями и поведением программы. - person Jim Balter; 29.06.2012
comment
Утверждения достаточно близки к тому же смыслу, ваше просто более многословно, и его сложнее расшифровать. - person Benjamin Lindley; 29.06.2012

Это не имеет ничего общего с реализацией malloc(). Союз в вашем примере использует одну и ту же ячейку памяти для хранения одного из трех «разных» указателей. Однако все указатели, независимо от того, на что они указывают, имеют одинаковый размер, который является собственным целочисленным размером архитектуры, на которой вы работаете, — 32 бита в 32-битных системах и 64 бита в 64-битных системах и т. д. Это связано с тем, что указатель — это адрес в памяти, который может быть представлен целым числом.

Допустим, ваш arr расположен по адресу 0x10000 (указатель на ваш указатель, если хотите). Допустим, malloc() находит вам ячейку памяти по адресу 0x666660. Вы присваиваете этому значению arr.array_f, что означает, что вы сохраняете указатель 0x666660 в ячейке 0x10000. Затем вы записываете свои числа с плавающей запятой в адреса от 0x666660 до 0x666688.

Теперь вы пытаетесь получить доступ к arr.array_i. Поскольку вы используете объединение, адрес arr.array_i совпадает с адресом arr.array_f и arr.array_c. Итак, вы снова читаете с адреса 0x10000 — и считываете указатель 0x666660. Поскольку это тот же самый указатель, который malloc вернул ранее, вы можете освободить его.

Тем не менее, попытка интерпретировать целые числа как текст или числа с плавающей запятой как целые числа и т. д. явно приведет к краху. Если arr.array_i[0] == 1, то arr.array_f[0] определенно не будет == 1 и arr.array_c[0] не будет иметь отношения к символу '1'. Вы можете попробовать "просмотреть" память таким образом в качестве упражнения (цикл и printf()) - но вы ничего не добьетесь.

person lyngvi    schedule 29.06.2012
comment
Но есть проблема в том, что char* и float* не обязательно должны быть одного размера. См. Все ли указатели данных имеют одинаковый размер в одна платформа. - person Bo Persson; 29.06.2012
comment
@BoPersson: они не должны быть. Но трудно представить, как free мог работать в такой системе, если только приведение к void * не приводило к чему-то очень странному. - person David Schwartz; 29.06.2012
comment
@David - Несколько необычно, но машины с адресацией слов могут иметь указатели одного размера для char* и void* и другого размера для int* и float*. Это противоречит Однако все указатели, независимо от того, на что они указывают, имеют одинаковый размер в этом ответе. Некоторые из нас также помнят, что в 16-битной Windows не было всех 16-битных указателей. - person Bo Persson; 29.06.2012
comment
Да, да, 16-битный x86 использовал 20-битные указатели, основанные на смещении 16-битного сегмента ‹‹ 4 плюс еще одно смещение. Я по глупости предположил, что мы говорим о системах, которым меньше 20 лет. - person lyngvi; 30.06.2012
comment
Хм. Никогда не видел системы с такой архитектурой, но, возможно, есть какие-то микропроцессоры, которые делают это. В этом случае указатели по-прежнему будут храниться в одном и том же месте (это объединение), но могут иметь разные размеры, и реализации stdlib malloc/free не будут использоваться, поскольку malloc() не будет иметь информацию о типе. необходимо выбрать, какую шину памяти использовать. Но да, трюк с видом здесь не сработает. - person lyngvi; 30.06.2012