Гарантируется, что структура указателей будет представлена ​​без битов заполнения? - PullRequest
2 голосов
/ 26 июня 2009

У меня есть связанный список, в котором хранятся группы настроек для моего приложения:

typedef struct settings {
  struct settings* next;
  char* name;
  char* title;
  char* desc;
  char* bkfolder;
  char* srclist;
  char* arcall;
  char* incfold;
} settings_row;
settings_row* first_profile = { 0 };

#define SETTINGS_PER_ROW 7

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

//putting values incrementally into the struct
void read_settings_file(settings_row* settings){
    char* field = settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW);
}

//accessing components by name
void settings_info(settings_row* settings){
    printf("Settings 'profile': %s\n", settings.title);
    printf("Description: %s\n", settings.desc);
    printf("Folder to backup to: %s\n", settings.bkfolder);
}

Но мне интересно, так как это все указатели (и в этой структуре будут только указатели), добавит ли компилятор заполнение к любому из этих значений? Они гарантированно находятся в таком порядке и не имеют ничего между значениями? Будет ли мой подход работать иногда, но периодически сбоит?

редактировать для уточнения

Я понимаю, что компилятор может дополнять любые значения структуры - но, учитывая природу структуры (структуры указателей), я подумал, что это не может быть проблемой. Поскольку наиболее эффективный способ адресации данных для 32-битного процессора - это 32-битные блоки, именно так компилятор добавляет значения в структуру (т. Е. Int, short, int в структуре добавляют 2 байта заполнения после короткого , чтобы превратить его в 32-битный блок, и выровнять следующее int со следующим 32-битным блоком). Но поскольку 32-разрядный процессор использует 32-разрядные адреса (а 64-разрядный процессор использует 64-разрядные адреса (я думаю)), заполнение будет совершенно ненужным, поскольку все значения структуры (адреса, которые эффективны по своей природе) в идеальных 32-битных чанках?

Я надеюсь, что некоторые гуру представления памяти / поведения компилятора могут пролить некоторый свет на то, будет ли у компилятора когда-нибудь причина дополнять эти значения

Ответы [ 8 ]

4 голосов
/ 26 июня 2009

В соответствии с правилами POSIX все указатели (как указатели функций, так и указатели данных) должны быть одного размера; только в соответствии с ISO C все указатели данных могут быть преобразованы в «void *» и обратно без потери информации (но указатели функций не должны быть преобразованы в «void *» без потери информации и наоборот).

Поэтому, если написано правильно, ваш код будет работать. Хотя это не совсем правильно написано! Рассмотрим:

void read_settings_file(settings_row* settings)
{
    char* field = settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW)
        ;
}

Предположим, вы используете 32-битный компьютер с 8-битными символами; Аргумент не сильно отличается, если вы используете 64-битные машины. Все присвоение 'field' является неправильным, поскольку settings + 4 является указателем на 5-й элемент (считая от 0) массива структур 'settings_row'. Что вам нужно написать это:

void read_settings_file(settings_row* settings)
{
    char* field = (char *)settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW)
        ;
}

Активация перед сложением имеет решающее значение!


C Стандарт (ISO / IEC 9899: 1999):

6.3.2.3. Указатели

Указатель на void может быть преобразован в или из указателя на любой незавершенный объект или объект тип. Указатель на любой неполный объект или тип объекта может быть преобразован в указатель на void и обратно снова; результат должен сравниться с исходным указателем.

[...]

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

4 голосов
/ 26 июня 2009

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

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

enum
{
    kName = 0,
    kTitle,
    kDesc,
    kBkFolder,
    kSrcList,
    kArcAll,
    kIncFold,
    kSettingsCount
};

typedef struct settings {
    struct settings* next;
    char *settingsdata[kSettingsCount];
} settings_row;

Установить данные:

settings_row myRow;
myRow.settingsData[kName] = "Bob";
myRow.settingsData[kDescription] = "Hurrrrr";
...

Чтение данных:

void read_settings_file(settings_row* settings){
    char** field = settings->settingsData;
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW);
}
2 голосов
/ 26 июня 2009

Это не гарантируется стандартом C. У меня есть подозрение, что у меня нет времени, чтобы проверить прямо сейчас в любом случае, что это гарантирует отсутствие заполнения между полями char *, то есть, что последовательные поля одного и того же типа в структуре гарантированно совместимы с макетом с массивом этого типа. Но даже если это так, вы сами между настройками * и первым символом *, а также между последним символом * и концом структуры. Но вы можете использовать offsetof для решения первой проблемы, и я не думаю, что вторая влияет на ваш текущий код.

Однако то, что вы хотите, почти наверняка гарантировано вашим компилятором, который где-то в своей документации изложит свои правила для разметки структуры и почти наверняка скажет, что все указатели на данные имеют размер слова, и что структура может быть размер 8 слов без дополнительного заполнения. Но если вы хотите написать очень переносимый код, вы должны использовать только гарантии в стандарте.

Порядок полей гарантирован. Я также не думаю, что вы увидите прерывистый сбой - AFAIK смещение каждого поля в этой структуре будет согласованным для данной реализации (имеется в виду сочетание компилятора и платформы).

Вы можете утверждать, что sizeof (settings *) == sizeof (char *) и sizeof (settings_row) == sizeof (char *) * 8. Если оба они выполняются, в структуре нет места для заполнения, поскольку поля не могут перекрываться. Если вы когда-нибудь попадете на платформу, где они не держатся, вы узнаете.

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

1 голос
/ 26 июня 2009

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

Кроме того, если вы хотите быть в большей безопасности, вы можете сделать это союзом.

1 голос
/ 26 июня 2009

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

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

Вы можете достичь своей цели с другим уровнем косвенности (что не решает?) Или с помощью временного массива, инициализированного для указания на различные члены структуры.

1 голос
/ 26 июня 2009

Хотя это и не дубликат, это, вероятно, ответит на ваш вопрос:

Почему sizeof для структуры не равен сумме sizeof каждого члена?

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

0 голосов
/ 26 июня 2009

Мне кажется, что этот подход создает больше проблем, чем решает. Когда вы прочитаете этот код через шесть месяцев, вы все еще будете осознавать все тонкости того, как компилятор дополняет структуру? Будет ли кто-то еще, кто не написал код?

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

Если вы слишком «умны» в отношении оптимизации своего кода, вы все равно получите более медленный код, поскольку компилятор не сможет его оптимизировать.

0 голосов
/ 26 июня 2009

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

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

Заполнение предназначено для обеспечения максимально эффективного доступа к полю на целевой архитектуре. Лучше не бороться с этим, если не нужно (то есть структура отправляется на диск или по сети).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...