Вопрос

Я пишу кроссплатформенную библиотеку C, но в конце концов я получаю ошибку в своих unittests, но только на компьютерах с Windows.Я отследил проблему и обнаружил, что она связана с выравниванием структур (я использую массивы структур для хранения данных для нескольких похожих объектов).Проблема в том, что:memset(sizeof(struct)) и настройка элементов structures один за другим приводят к разным побайтовым результатам, и поэтому memcmp() возвращает результат "не равно".

Вот код для иллюстрации:

#include <stdio.h>
#include <string.h>

typedef struct {
    long long      a;
    int            b;
} S1;

typedef struct {
    long           a;
    int            b;
} S2;

S1 s1, s2;

int main()
{
    printf("%d %d\n", sizeof(S1), sizeof(S2));

    memset(&s1, 0xFF, sizeof(S1));
    memset(&s2, 0x00, sizeof(S1));

    s1.a = 0LL; s1.b = 0;

    if (0 == memcmp(&s1, &s2, sizeof(S1)))
        printf("Equal\n");
    else
        printf("Not equal\n");

    return 0;
}

Этот код с MSVC 2003 @ Windows выдает следующий вывод:

16 8
Not equal

Но тот же код с GCC 3.3.6 @ Linux работает так, как ожидалось:

12 8
Equal

Это делает мое модульное тестирование очень сложным.

Правильно ли я понимаю, что MSVC использует размер самого большого собственного типа (long long) для определения соответствия структуре?

Кто-нибудь может дать мне совет, как я могу изменить свой код, чтобы сделать его более устойчивым к этой странной проблеме выравнивания?В моем реальном коде я работаю с массивами структур с помощью универсальных указателей для выполнения memset / memcmp, и я обычно не знаю точного типа, у меня есть только значение sizeof (struct).

Это было полезно?

Решение

То, что мы сделали, использовало пакет #pragma, чтобы указать, насколько большими должны быть объекты:

#pragma pack(push, 2)

typedef struct {
    long long      a;
    int            b;
} S1;

typedef struct {
    long           a;
    int            b;
} S2;

#pragma pack(pop)

Если вы сделаете это, структуры будут одинакового размера на обеих платформах.

Другие советы

Ваш юнит тест не соответствует ожиданиям. Он (или код, который он проверяет) не должен сканировать буфер структуры за байтом. Для точных байтов код должен явно создать байтовый буфер в стеке или в куче и заполнить его выдержками из каждого члена. Выдержки могут быть получены независимым от CPU порядком байтов путем использования операции сдвига вправо по целочисленным значениям и приведения результата к типу байта, например (unsigned char).

Кстати, ваш фрагмент пишет за s2. Вы можете исправить это, изменив это

memset(&s2, 0x00, sizeof(S1));

s1.a = 0LL; s1.b = 0;

if (0 == memcmp(&s1, &s2, sizeof(S1)))

к этому,

memset(&s2, 0x00, sizeof(S2));

s1.a = 0LL; s1.b = 0;

if (0 == memcmp(&s1, &s2, sizeof(S2)))

но результат технически "неопределенный" потому что выравнивание элементов в структурах зависит от компилятора.

Руководство по GCC:

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

Кроме того, это обычно вводит элемент заполнения (т.е.заполняющие байты для выравнивания структуры).Вы можете использовать #pragma с аргументом в пользу packed.Примечание, #pragmas - это НЕ переносимый способ работы.К сожалению, это также, пожалуй, единственный способ работы в вашем случае.

Ссылки:Здесь GCC по выравниванию структуры.MSDN выравнивание структуры.

Обратите внимание, что это не «странная» проблема выравнивания. MSVC решил обеспечить выравнивание структуры на 64-битной границе, поскольку он имеет 64-битный член, поэтому он добавляет некоторые отступы в конце структуры, чтобы гарантировать, что массивы этих объектов будут правильно выровнены для каждого элемента. Я на самом деле удивлен, что GCC не делает то же самое.

Мне любопытно, что делает ваше модульное тестирование, что затрудняет это - большую часть времени выравнивание элементов структуры не требуется, если вам не нужно сопоставлять двоичный формат файла или проводной протокол, или вам действительно нужно уменьшить объем памяти, используемой структурой (особенно используется во встроенных системах). Не зная, что вы пытаетесь сделать в своих тестах, я не думаю, что хорошее предложение может быть дано. Упаковка структуры может быть решением, но она требует определенных затрат - производительности (особенно на платформах, отличных от Intel) и переносимости (способ настройки структуры упаковки может отличаться от компилятора к компилятору). Это может не иметь значения для вас, но в вашем случае может быть лучший способ решения проблемы.

Вы можете сделать что-то вроде

#ifdef _MSC_VER
#pragma pack(push, 16)
#endif

/* your struct defs */

#ifdef _MSC_VER
#pragma pack(pop)
#endif

чтобы дать директиве компилятора принудительное выравнивание

Или перейдите в параметры проекта и измените выравнивание структуры по умолчанию [в разделе «Генерация кода»)

Заполнение структуры для 64-битных значений отличается в разных компиляторах. Я видел различия даже между целями gcc.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top