Является ли неопределенным поведение приведения из структуры с гибким членом массива к другой идентичной без? - PullRequest
2 голосов
/ 21 января 2020

Я хочу иметь структуру переменного размера, но я хочу встроить экземпляр структуры с определенным размером в другую структуру. Вот идея:

struct grid {
    size_t width, height;
    int items[ /* width * height */ ];
};

struct grid_1x1 {
    size_t width, height;
    int items[1];
};

struct grid_holder {
    struct grid_1x1 a, b;
};

int main(void)
{
    struct grid_holder h = {
        .a = { .width = 1, .height = 1, .items = { 0 } },
        .b = { .width = 1, .height = 1, .items = { 0 } },
    };
    struct grid *a = (struct grid *)&h.a, *b = (struct grid *)&h.b;
}

Если весь мой код предполагает, что items член struct grid имеет width * height элементов, можно ли разыгрывать a и b, как я описал выше ?

Другими словами, всегда ли элемент гибкого массива с одним элементом имеет то же смещение и размер, что и элемент массива фиксированного размера с одним элементом, если структуры в остальном идентичны? Я хотел бы получить ответ на основе стандарта C99. Если смещения могут отличаться, есть ли другой способ достичь моей цели, указанной в начале?

Ответы [ 2 ]

4 голосов
/ 21 января 2020

Да, поведение не определяется стандартом C.

Правило в C 2018 6.5 7 или C 1999 6.5 7 о том, какие типы могут использоваться для доступа к объекту, не только о том, как объекты выложены и представлены. Таким образом, предложение в вопросе «Другими словами, всегда ли элемент гибкого массива с одним элементом имеет то же смещение и размер, что и элемент массива фиксированного размера с одним элементом, учитывая, что структуры в остальном идентичны?» это неверно. Наличие одинакового смещения и размера, даже с одинаковыми определениями структуры, не делает структуры совместимыми для псевдонимов.

Разные структуры намеренно являются разными типами. Рассмотрим эти два типа:

typedef struct { double real, imaginary; } Complex;
typedef struct { double x, y; } Coordinates;

Эти структуры имеют идентичные определения (за исключением имен членов, но справедливо следующее, даже если их имена были идентичны), но они являются разными и несовместимыми типами согласно C стандарт. Это означает, что в такой подпрограмме, как:

double foo(Complex *a, Coordinates *b)
{
    a->real = 3; a->imaginary = 4;
    b->x = 5; b->y = 6;
    return sqrt(a->real*a->real + a->imaginary*a->imaginary);
}

, компилятору разрешено оптимизировать последний оператор до return 5; на том основании, что b->x = 5; b->y = 6; не мог изменить a, поскольку a и b не может указывать на один и тот же объект, или, если они есть, поведение b->x = 5; b->y = 6; не определено.

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

В отличие от приведенного выше примера с различными, но идентично определенными структурами, когда у нас есть несколько указателей на один и тот же тип структуры, компилятор не может предположить, что a и b не являются псевдонимами (разными именами) для одного и того же объекта. В:

double foo(Complex *a, Complex *b)
{
    a->real = 3; a->imaginary = 4;
    b->real = 5; b->imaginary = 6;
    return sqrt(a->real*a->real + a->imaginary*a->imaginary);
}

компилятор не может предположить, что возвращаемое значение равно 5, поскольку a и b могут указывать на один и тот же объект, и в этом случае b->real = 5; b->imaginary = 6; изменяет содержимое a.

1 голос
/ 21 января 2020

Есть две отдельные проблемы, о которых вам нужно беспокоиться:

  1. Стандарт позволяет реализациям размещать произвольные количества заполнения между элементами структуры, при условии, что только общее количество заполнения перед любым На элемент структуры влияют только типы этого и предыдущих членов. Для этой цели массивы разных размеров считаются разными типами. По крайней мере, теоретически, некоторые реализации, нацеленные на странные архитектуры, могут изменять заполнение перед массивом в зависимости от его размера. Например, на платформе, где адреса идентифицируют 32-битные слова, но есть инструкции для чтения и записи в них 8-битных блоков, реализация, заданная struct x1 { long l; char a,b[4], c;};, может решить дополнить начало b, так что все это подходит одно слово, даже если та же самая реализация, заданная struct x1 { long l; char a,b[5], c;};, не добавит такой отступ (поскольку части b будут разделены между двумя словами независимо). Мне неизвестно о каких-либо реализациях, которые на самом деле делают такие вещи, но Комитет, вероятно, ожидал, что единственный случай, когда такая слабость будет иметь значение, будет, если компиляторы будут разрабатываться и использоваться на таких платформах, и в этом случае люди, работающие с такими платформами, будут быть более способным, чем Комитет, судить о плюсах и минусах различных подходов к заполнению.

  2. Хотя правило общей начальной последовательности по всем признакам предназначалось для того, чтобы указатель на один тип конструкции мог быть используется для проверки любой части общей начальной последовательности других структурных типов (такая возможность задокументирована в Справочном руководстве 1974 C, и после того, как объединения были добавлены в язык, компиляторам пришлось бы go покинуть их путь к поддерживают такое использование с объединениями, не поддерживая его с помощью структурных указателей), авторы clang и g cc считают нарушенным любой код, который будет опираться на такую ​​обработку, и активно отказываются поддерживать такой код, кроме как с помощью th e -fno-strict-aliasing flag.

Я бы расценил первую проблему как чисто теоретическую, но вторая проблема означает, что любой код, который будет пытаться использовать указатель для доступа к множеству отдельно объявленных структур Типы должны использовать опцию -fno-strict-aliasing при сборке с g cc или clang. Это не должно быть проблемой, но вторая проблема означает, что любой, чей код может быть использован с clang или g cc, должен убедиться, что любой, использующий эти компиляторы, знает о необходимости -fno-strict-aliasing (т.е. тупой ") флаг. Насколько я могу судить, компиляторы, предназначенные для платных клиентов, поддерживают конструкции с пользой даже при использовании -fstrict-aliasing, потому что их поддержка полезна и не сложна, но сопровождающие g cc и clang идеологически против такой поддержки.

...