Освободить / удалить объединение 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()), но ничего не добьетесь.

Другие вопросы по тегам