Можно ли преобразовать выровненные структуры внутри объединения в объединение для доступа к выровненным полям?

Я пытаюсь понять, что именно вы получаете от сервитута для выровненных переменных в C99:

Исключение из строгого правила псевдонимов в C из 6.5.2.3 Члены структуры и объединения

Дает ли это вам карт-бланш на приведение к этому союзу, если исходная запись была сделана через указатель на одну из выровненных структур, как показано ниже?

#include <stdio.h>
#include <stdlib.h>

struct Foo { char t; int i; };

struct Bar { char t; float f; };

union FooBar {
    struct Foo foo;
    struct Bar bar;
};

void detector(union FooBar *foobar) {
    if (((struct Foo*)foobar)->t == 'F')
       printf("Foo %d\n", ((struct Foo*)foobar)->i);
    else
       printf("Bar %f\n", ((struct Bar*)foobar)->f);
}

int main() {
    struct Foo *foo = (struct Foo*)malloc(sizeof(struct Foo));
    struct Bar *bar = (struct Bar*)malloc(sizeof(struct Bar));

    foo->t = 'F';
    foo->i = 1020;
    detector((union FooBar*)foo);

    bar->t = 'B';
    bar->f = 3.04;
    detector((union FooBar*)bar);

    return 0;
}

Обратите внимание, что во втором вызове t было записано как "bar's t", но затем, чтобы определить, к какому типу оно относится, детектор считывает его как "foo's t"

Моя реакция, исходящая от C++, будет заключаться в том, что вы сможете сделать это только в том случае, если вы «в первую очередь выделили это как объединение FooBar». Мне нелогично представлять это как законное, но для динамических распределений в C такого нет. Итак, если вы не можете этого сделать, что именно вы можете сделать с динамическим выделением памяти, таким как выше, в этом исключении?


person HostileFork says dont trust SE    schedule 25.11.2015    source источник
comment
Не переводите результат malloc & friends в C или void * в/из других указателей вообще.   -  person too honest for this site    schedule 25.11.2015
comment
@Olaf Я пишу код, предназначенный для компиляции как на C, так и на C++. Поэтому я программирую на несуществующем языке C/C++, который включает в себя возможность использовать C++ в качестве статического анализатора при создании технологии, которая также может компилироваться под C89. Это мое хобби...   -  person HostileFork says dont trust SE    schedule 25.11.2015
comment
Таким образом, вы не используете VLA и другие функции C99/C11. И вас не волнует различная семантика других конструкций. Плохой подход. Но так как вы не занимаетесь этим профессионально, я не возражаю.   -  person too honest for this site    schedule 25.11.2015
comment
@Olaf: я не совсем понимаю отношение.   -  person gnasher729    schedule 25.11.2015
comment
@ gnasher729: Ограничение C до C89 только для того, чтобы он компилировался с C ++ для статического анализа кода, - просто плохая идея. Он не использует функции, добавленные с тех пор, чтобы сделать ваш код более безопасным и удобным в обслуживании. Но если вы умеете пользоваться только молотком, любая проблема выглядит как гвоздь.   -  person too honest for this site    schedule 25.11.2015
comment
@Olaf Спасибо за последнее слово: Аналогичный аргумент можно привести о C ++ против самого C. Мощность, шрифтобезопасность, выразительность и т. д. превосходят С на много миль, особенно в наши дни. Но есть веские причины, по которым люди не используют его для некоторых задач. Затем аналогичный аргумент о безопасности и элегантности Haskell против дерьма императивного C++. И так далее. Учитывая грязь таких дискуссий, лучше сохранить комментарии для действительно заданного вопроса, когда вопрос совершенно ясен.   -  person HostileFork says dont trust SE    schedule 25.11.2015


Ответы (2)


Если вы сделали что-то вроде этого:

struct Foo foo;
struct Bar bar;
...
detector((union FooBar*)&foo);
detector((union FooBar*)&bar);

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

Но поскольку в вашем случае вы динамически выделяете память для каждой структуры, выравнивание не является проблемой.

Со страницы руководства для malloc:

Для calloc() и malloc() возвращаемое значение является указателем на выделенную память, которая соответствующим образом выровнена для любого типа переменной, или NULL, если запрос не выполнен.

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

person dbush    schedule 25.11.2015
comment
Хорошо... ну, я не думал конкретно о выравнивании, но об общей неприязни к использованию другого выделенного типа, а затем к его приведению, так приятно слышать, что есть правило, которое поможет с этим. И меня особенно интересовал этот вопрос о malloc, а не об использовании объединения в качестве переменной стека... так что вы говорите, что предложение о calloc/malloc делает приведенный выше код приемлемым в C... и что было бы нормально, если бы я выделил как объединение в первую очередь в случае с выделением стека? - person HostileFork says dont trust SE; 25.11.2015
comment
@HostileFork Да, если структура была выделена динамически сама по себе (т. е. не является частью динамически выделенного массива, и в этом случае выравнивание будет нарушено), то вы не должны беспокоиться о проблемах с выравниванием. - person dbush; 25.11.2015
comment
Стоит иметь в виду, что этот отрывок не означает, что вы можете просто выделить struct Foo и ожидать, что приведение его к struct Bar волшебным образом сработает, особенно из-за возможных различий в заполнении структуры и различиях в размерах между int и float. По этой причине, опять же, выделяйте union FooBar, если вы хотите быть в большей безопасности, а не отдельные экземпляры struct Foo и struct Bar. Это один дополнительный уровень косвенности в исходном коде, но я бы сказал, что оно того стоит, если вам нужна такая возможность. - person ; 25.11.2015
comment
На самом деле я решил реструктурировать и делать то, что я делал, по-другому - из-за неубедительных условий, при которых это могло бы работать... в основном из-за отзывов здесь. Спасибо за напоминание о проблеме выравнивания malloc! - person HostileFork says dont trust SE; 27.11.2015

Если Foo и Bar имеют разное выравнивание, вам не следует делать это уже только по этой причине. union будет иметь максимальное выравнивание из двух, и приведение к результату с меньшим значением даст вам union, которое неправильно выровнено.

Ваш код не является хорошим примером правил псевдонимов, потому что здесь у вас в основном нет псевдонимов. Но в целом приведение к другому типу всегда плохо в тех случаях, когда у вас может быть алиасинг. Ваш компилятор может делать предположения о двух (или более) указателях, которые видит код. Если они относятся к разным типам (за исключением типов char), компилятор может предположить, что они никогда не указывают на один и тот же объект.

person Jens Gustedt    schedule 25.11.2015
comment
В @dbush answer он предполагает, что для одиночной выделенной структуры выравнивание не будет проблемой... как malloc возвращает память, которая соответствующим образом выровнена для любого типа переменной. Это предполагает, что приведенный выше код в порядке ... вы согласны? - person HostileFork says dont trust SE; 25.11.2015
comment
@HostileFork, если и только если он получен malloc. Если вы затем попытаетесь использовать тот же код для переменных или, возможно, если вы используете new C++ (поскольку вы, похоже, также хотите использовать C++), вы можете столкнуться с ошибкой BUS. - person Jens Gustedt; 25.11.2015
comment
Хорошо спасибо. (Использование компилятора C++, но не распределителя C++ - если это не отладочный код сборки, выполняющий какую-то параллельную проверку низкоуровневого материала и желающий использовать для этого стандартную библиотеку C++...) - person HostileFork says dont trust SE; 25.11.2015
comment
Ни gcc, ни clang не могут надежно обработать все случаи, когда хранилище, используемое как один тип, позже используется как другой, даже если каждое чтение выполняется с использованием того же типа, что и предыдущая запись, и даже если каждое значение lvalue другого типа получено из общего lvalue после любой предыдущей операции с другим типом и отмененной перед любой последующей операцией с новым типом. Даже если код не использует псевдонимы, как написано, gcc может оптимизировать код, чтобы ввести псевдонимы, но затем не справиться с его последствиями. - person supercat; 21.06.2018