Совместимость с указателем структуры - PullRequest
9 голосов
/ 02 января 2012

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

typedef struct Struct1
{
    short a_short;
    int id;
} Struct1;

typedef struct Struct2
{
    short a_short;
    int id;
    short another_short;
} Struct2;

Безопасно ли разыгрывать от Struct2 * до Struct1 *? Что спецификация ANSI говорит об этом? Я знаю, что некоторые компиляторы имеют возможность переупорядочивать поля структур для оптимизации использования памяти, что может сделать эти две структуры несовместимыми. Есть ли способ убедиться, что этот код будет действительным, независимо от флага компилятора?

Спасибо!

Ответы [ 6 ]

5 голосов
/ 02 января 2012

Насколько я знаю, это безопасно.

Но гораздо лучше, если это возможно, сделать:

typedef struct {
    Struct1 struct1;
    short another_short;
} Struct2;

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

3 голосов
/ 02 января 2012

Скорее всего, это будет работать. Но вы очень правильно спрашиваете, как вы можете быть уверены, что этот код будет действительным. Итак: где-то в вашей программе (возможно, при запуске) встроить кучу операторов ASSERT, которые гарантируют, что offsetof( Struct1.a_short ) равен offsetof( Struct2.a_short ) и т. Д. Кроме того, некоторые программисты, кроме вас, могут однажды изменить одну из этих структур, но не другой, так лучше, чем потом сожалеть.

3 голосов
/ 02 января 2012

Типы указателей struct всегда имеют одинаковое представление в C.

(C99, 6.2.5p27) "Все указатели на типы конструкций должны иметь одинаковые требования к представлению и выравниванию друг с другом. "

И члены в типах структуры всегда в порядке в C.

(C99, 6.7.2.1p5) "структура - это тип, состоящий из последовательности члены, чье хранилище размещено в упорядоченной последовательности "

2 голосов
/ 14 июня 2018

Спецификация языка содержит следующую гарантию

6.5.2.3 Структура и члены профсоюза
6 Одна специальная гарантия предоставляется для упрощения использования союзов: если профсоюз содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объединение В настоящее время объект содержит одну из этих структур, разрешается проверять общие Начальная часть любого из них где угодно, что объявление о завершенном типе объединения виден Две структуры имеют общую начальную последовательность, если соответствующие члены иметь совместимые типы (и, для битовых полей, одинаковой ширины) для последовательности из одного или нескольких первоначальные члены.

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

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

6,5 выражений
7 Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет одно из следующих значений: следующие типы:
- тип, совместимый с эффективным типом объекта,
- квалифицированная версия типа, совместимого с эффективным типом объекта,
- тип, который является типом со знаком или без знака, соответствующим действующему типу объект
- тип, который является типом со знаком или без знака, соответствующим квалифицированной версии эффективный тип объекта,
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих члены (включая, рекурсивно, член субагрегата или автономного объединения), или
- тип символа.

Единственный вопрос здесь - доступ к

((Struct1 *) struct2_ptr)->a_short

представляет собой доступ ко всему объекту Struct2 (в этом случае это нарушение 6.5 / 7 и оно не определено) или просто доступ к объекту short (в этом случае он может быть точно определен).

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

1 голос
/ 14 июня 2018

Нет, стандарт не позволяет этого;доступ к элементам объекта Struct2 через указатель Struct1 - неопределенное поведение.Struct1 и Struct2 не являются совместимыми типами (как определено в 6.2.7) и могут быть дополнены по-разному, и доступ к ним через неправильный указатель также нарушает правила наложения имен.

Единственный способ, которым что-то подобное гарантировано работаеткогда Struct1 включен в Struct2 в качестве его начального члена (6.7.2.1.15 в стандарте), как в открутить ответ .

0 голосов
/ 02 января 2012

Да, это нормально!

Пример программы следующий.

#include <stdio.h>

typedef struct Struct1
{
    short a_short;
    int id; 
} Struct1;

typedef struct Struct2
{
    short a_short;
    int id; 
    short another_short;
} Struct2;

int main(void) 
{

    Struct2 s2 = {1, 2, 3}; 
    Struct1 *ptr = &s2;
    void *vp = &s2;
    Struct1 *s1ptr = (Struct1 *)vp;

    printf("%d, %d \n", ptr->a_short, ptr->id);
    printf("%d, %d \n", s1ptr->a_short, s1ptr->id);

    return 0;
}
...