Освободить / удалить объединение malloc/new Array в 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. Я также предполагаю, что это будет работать при выделении array_c, хотя, в отличие от int и float, массивы вряд ли будут иметь одинаковый размер.
Тест можно повторить с новым и удалить, которые похожи. Я предполагаю, что это также будет работать.
Я предполагаю, что спецификации языка будут ненавидеть меня за это, но я ожидаю, что это сработает. Это напоминает мне о том, что "не удаляйте новый указатель, приведенный к void *, даже если это массив, а не объект".
Итак, вопросы: что спецификация говорит об этом? Я кратко проверил, но не смог найти ничего, что конкретно касается этого случая. Насколько опрометчиво это так или иначе - с функциональной точки зрения (я понимаю, что это ужасно с точки зрения ясности).
Это чисто любопытный вопрос для педантичных целей.
3 ответа
Ты точно прав. Это нарушает правила:
Когда значение сохраняется в элементе объекта типа объединения, байты представления объекта, которые не соответствуют этому элементу, но соответствуют другим элементам, принимают неопределенные значения, но значение объекта объединения не должно, таким образом, становиться представление ловушки.
- стандарт ISO/IEC 9899, раздел 6.2.6.1
Однако то, как обычно выполняются реализации, "случайно" будет работать должным образом. поскольку free
занимает void *
параметр будет преобразован в void *
перейти к free
, Поскольку все указатели расположены по одному адресу и все преобразования в void *
не вносить изменений в их стоимость, конечная стоимость передается free
будет таким же, как если бы правильный член был передан.
Теоретически, реализация может отследить, к какому члену объединения был получен последний доступ, и повредить значение (или вывести программу из строя, или сделать что-нибудь еще), если вы читаете член, отличный от того, который вы в последний раз написали. Но, насколько мне известно, никакая реальная реализация не делает ничего подобного.
Это неопределенное поведение, потому что вы обращаетесь к другому члену, чем вы установили. Это может сделать буквально все, что угодно.
На практике это обычно работает, но на это нельзя полагаться. Компиляторы и наборы инструментов не являются преднамеренно злыми, но были случаи, когда оптимизации взаимодействовали с неопределенным поведением для получения совершенно неожиданных результатов. И, конечно, если вы когда-либо работаете в системе с другой реализацией malloc, она, вероятно, взорвется.
Это не имеет ничего общего с реализацией 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'. Вы можете попробовать "просмотреть" память таким образом в качестве упражнения (loop и printf()), но ничего не добьетесь.