проблема длинного выравнивания (MSVC против GCC) - PullRequest
1 голос
/ 04 марта 2009

Я пишу кроссплатформенную библиотеку C, но в итоге у меня возникла ошибка в моих тестах юнитов, но только на компьютерах с Windows. Я отследил проблему и обнаружил, что она связана с выравниванием структур (я использую массивы структур для хранения данных для нескольких похожих объектов). Проблема заключается в следующем: memset (sizeof (struct)) и установка элементов структуры один за другим приводят к разным байтовым результатам, и поэтому 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).

Ответы [ 6 ]

4 голосов
/ 27 марта 2009

Ваш модульный тест неверен. Он (или код, который он проверяет) не должен сканировать буфер структуры за байтом. Для точных байтов код должен явно создать байтовый буфер в стеке или в куче и заполнить его выдержками из каждого члена. Выдержки могут быть получены независимым от 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)))

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

2 голосов
/ 04 марта 2009

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

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

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

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

1 голос
/ 04 марта 2009

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

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

1 голос
/ 04 марта 2009

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

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

/* your struct defs */

#ifdef _MSC_VER
#pragma pack(pop)
#endif

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

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

1 голос
/ 04 марта 2009

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

#pragma pack(push, 2)

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

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

#pragma pack(pop)

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

0 голосов
/ 05 марта 2009

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

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

...