Является ли листинг массива однородной структурированной переносимой в C?

17

Рассмотрим следующую однородную структуру:

struct myStruct {
    void* a;
    char* b;
    int* c;
};

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

С учетом этой структуры следующий код будет действительным и переносимым на C99?

int main()
{
    void* x = NULL;
    char* y = "hello";
    int* z = malloc(sizeof(int) * 10);
    z[2] = 10;

    void** myArray = malloc(sizeof(void*) * 3);
    myArray[0] = x;
    myArray[1] = y;
    myArray[2] = z;

    struct myStruct* s = (struct myStruct*)myArray;

    printf("%p %s %d\n", s->a, s->b, s->c[2]);

    return 0;
}

Я понимаю, что structs часто добавляет дополнение между компонентами, чтобы сохранить согласованность структуры, однако, поскольку типы указателей одинаковы, безопасно ли предположить, что никакие дополнения не будут добавлены? Я не обязательно спрашиваю, есть ли 100% -ная гарантия (я понимаю, что это полностью конкретная реализация, и что компилятор может добавлять дополнения для неясных причин), больше я прошу указать, какие дополнения могут быть добавлены в однородную структуру, если есть какие-либо причины.

    
задан Braden Steffaniak 21.03.2017 в 08:01
источник

4 ответа

19

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

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

Хорошим примером платформы, где размеры указателя отличаются, является DOS (который все еще активно используется, например, во встроенных системах) и другие 16-разрядные сегментированные системы.

    
ответ дан Some programmer dude 21.03.2017 в 08:07
  • Хорошо, что на него отвечает. будет принимать, когда срок превысит. Если они все недействительны *, то это будет переносимо? –  Braden Steffaniak 21.03.2017 в 08:10
  • @BradenSteffaniak Это немного сложнее ответить. Я хочу сказать, наверное, но я бы не стал делать ставку на него. –  Some programmer dude 21.03.2017 в 08:12
  • @Some programmer dude: На самом деле, если память служит, это было совершенно безопасно в DOS, по крайней мере, компилятор Turbo C, который я использовал, а также встроенные компиляторы, которые я использовал с разрозненными (не функциональными) размерами указателей. Обычно разные пространства памяти помечены явными квалификаторами (рядом и далеко в случае DOS), тогда как немаркированные объекты достигают одного и того же пространства памяти. Это действительно нарушает всевозможные допущения, хотя, например, я в настоящее время пишу код в системе, где unecorated указатели / size_t / intptr_t являются 8-битными, в то время как большинство из них достигается через расширенные 16-битные указатели. –  doynax 21.03.2017 в 10:14
  • Кроме того, не является ли компилятор совершенно свободным вставлять байты элемента внутри структуры, если он считает, что макет будет более эффективным для решения? Вероятно, вряд ли с указателями, но для любого другого типа данных это может быть проблемой. –  T.E.D. 21.03.2017 в 15:50
  • @BradenSteffaniak: Он был бы переносимым (в том смысле, что текущие компиляторы будут поступать правильно), но не согласуется с тем, что теоретические дополнения могут существовать между членами структуры. –  alecov 21.03.2017 в 17:49
12

Код нарушает правила псевдонимов. Типы void * и struct myStruct несовместимы, и в этом случае исключения не применяются. Разновидность указателя struct в выражении printf вызывает неопределенное поведение:

printf("%p %s %d\n", s->a, s->b, s->c[2]);

Это верно, даже если структура имеет тот же размер, что и три указателя void, не имеет отступов и имеет те же требования к выравниванию.

    
ответ дан 2501 21.03.2017 в 09:43
  • Справедливая точка. Есть ли у вас примеры контринтуитивного / неопределенного поведения от разыменования однородного указателя структуры, который был отлит из массива? –  Braden Steffaniak 21.03.2017 в 10:03
  • @BradenSteffaniak Обычно компилятор будет использовать старые значения вместо перезагрузки новых. Но не полагайтесь на это. Такое поведение является ошибкой, которая может привести к плохим вещам. –  2501 21.03.2017 в 10:09
  • @BradenSteffaniak: Как gcc, так и clang, похоже, распознают форму псевдонимов, используемую здесь, даже в тестах, где большинство других форм псевдонимов ломаются, но авторы gcc заявили довольно решительно, что такие прецеденты ничего не значат. Если в документации gcc где-то не говорится, что она поддерживает такое наложение, даже если стандарт не требует этого, я не вижу причин ожидать, что будущие версии не откажутся от такой поддержки. –  supercat 21.03.2017 в 17:18
1

Ничто не мешает компилятору использовать различное выравнивание для структур и массивов. Например, в SPARC с GCC можно выровнять структуры с границей 4 или 8 байтов, используя -mfaster-structs / -mnofaster-structs переключатели, в то время как массивы указателей выровнены по размеру их элемента (опять же, 4 или 8 байтов, в зависимости от тип указателей, которые вы используете). В случае несоответствия, ваш личный

struct myStruct* s = (struct myStruct*)myArray;

приведет к недопустимому указателю и приведет к UB.

    
ответ дан Dmitry Grigoryev 21.03.2017 в 13:18
0
  

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

Нет, совсем нет. Заполнение (упаковка) является полностью несвязанной проблемой для однородности структуры или нет. Он работает просто «до тех пор, пока член короче текущей упаковки, он дополняется, чтобы компенсировать». Например, однородный struct сингулярного char s почти всегда будет сильно дополнен.

Вы можете сделать #pragma pack(16) , а ваши члены структуры будут разнесены на 16 байт. Или вы можете установить упаковку в 1 байт, а затем смешать void* s с единственным char s и до сих пор не иметь дополнения (но вместо этого указывается головная боль указателя).

    
ответ дан Agent_L 21.03.2017 в 15:50