Почему gcc и NVCC (g ++) видят два разных размера структуры?

Я пытаюсь добавить CUDA в существующую однопоточную программу на C, написанную в конце 90-х годов.

Для этого мне нужно смешать два языка, C и C ++ (nvcc — это компилятор c ++).

Проблема в том, что компилятор C ++ видит структуру как определенный размер, в то время как компиляция C видит ту же структуру как немного другой размер. Это плохо. Я действительно озадачен этим, потому что не могу найти причину расхождения в 4 байта.

/usr/lib/gcc/i586-suse-linux/4.3/../../../../i586-suse-linux/bin/ld: Warning: size of symbol `tree' changed from 324 in /tmp/ccvx8fpJ.o to 328 in gpu.o

Мой C ++ выглядит так

#include <stdio.h>
#include <stdlib.h>
#include "assert.h"extern "C"{
#include "structInfo.h" //contains the structure declaration
}
...

и мои файлы C выглядят как

#include "structInfo.h"...

с structInfo.h выглядит

struct TB {
int  nbranch, nnode, root, branches[NBRANCH][2];
double lnL;
}  tree;
...

Мой make-файл выглядит так

PRGS =  prog
CC = cc
CFLAGS=-std=gnu99 -m32
CuCC = nvcc
CuFlags =-arch=sm_20
LIBS = -lm -L/usr/local/cuda-5.0/lib -lcuda -lcudart
all : $(PRGS)
prog:
$(CC) $(CFLAGS) prog.c gpu.o $(LIBS) -o prog
gpu.o:
$(CuCC) $(CuFlags) -c gpu.cu

Некоторые люди спрашивали меня, почему я не использовал другой вариант компиляции хоста. Я думаю, что вариант компиляции хоста устарел с 2 выпуска назад? Также он никогда не делал то, что говорил.

nvcc warning : option 'host-compilation' has been deprecated and is ignored

6

Решение

Графические процессоры требуют естественного выравнивания для всех данных, например, 4-байтовое int должно быть выровнено с 4-байтовой границей, а 8-байтовый двойной или длинный длинный должен иметь 8-байтовое выравнивание. CUDA применяет это и для кода хоста, чтобы обеспечить максимально возможную совместимость структур между частями кода хоста и устройства. Процессоры x86, с другой стороны, обычно не требуют естественного выравнивания данных (хотя снижение производительности может быть вызвано отсутствием выравнивания).

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

Чтобы заставить gcc выровнять двойники так же, как это делает CUDA, передайте флаг -malign-double,

13

Другие решения

Похоже на то, что два компилятора применяют разные отступы: один работает с 4-байтовым выравниванием, а другой — как минимум с 8-байтовым выравниванием. Вы должны быть в состоянии принудительно выровнять выравнивание в зависимости от компилятора #pragma директивы (проверьте документацию вашего компилятора о конкретных #pragma).

5

Нет гарантии, что два разных компилятора C будут использовать одно и то же представление для одного и того же типа — если только они оба не соответствуют некоторому внешнему стандарту (ABI), который определяет представление достаточно подробно.

Это, скорее всего, разница в заполнении, когда один компилятор требует double быть выровненным по 4 байта, а другой требует, чтобы он был выровненным по 8 байтов. Оба варианта совершенно верны в том, что касается стандартов C и C ++.

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

printf("nbranch: size %3u offset %3u\n",
(unsigned)sizeof tree.nbranch,
(unsigned)offsetof(struct TB, nbranch));
/* and similarly for the other members */

Там может быть специфичным для компилятора способом указать другое выравнивание, но такие методы не всегда безопасно.

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

Или вы можете изменить свое определение структуры так, чтобы оба компилятора выстроили его одинаково. Размещение double Первый член, скорее всего, будет работать. Это по-прежнему не гарантируется, и это может порвать с будущими версиями любого компилятора, но это наверное достаточно хорошо.

Не забывайте, что в самом конце конструкции также может быть заполнение; иногда это необходимо для обеспечения правильного выравнивания массивов конструкций. смотреть на sizeof (struct TB) и сравните его с размером и смещением последнего объявленного члена.

Другая возможность: вставить явные неиспользуемые элементы, чтобы обеспечить согласованное выравнивание. Например, предположим, если у вас есть:

struct foo {
uint16_t x;
uint32_t y;
};

и один компилятор ставит y в 16 битах, и другой помещает это в 32 бита с 16 битами заполнения. Если вы измените определение на:

struct foo {
uint16_t x;
uint16_t unused_padding;
uint32_t y;
};

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

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

2
По вопросам рекламы [email protected]