【问题标题】:Why do gcc and NVCC (g++) see two different structure sizes?为什么 gcc 和 NVCC (g++) 会看到两种不同的结构大小?
【发布时间】:2012-11-26 18:37:36
【问题描述】:

我正在尝试将 CUDA 添加到 90 年代后期某个时间编写的现有单线程 C 程序中。

为此,我需要混合两种语言,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 版前已被弃用?还有it never appeared to do what it said it would do

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

【问题讨论】:

  • 填充是否存在差异?您确定 nvcc 和 gcc(我认为您使用的是 cc)兼容吗?编辑:实际上,阅读与 nvcc 填充问题相关的内容,待命
  • 这是 32 位还是 64 位平台?你可以试试moving the parameters around 看看是否有效?
  • @EsaLakaniemi 这是一个 32 位平台。
  • 你大概可以使用nvcc编译C.--host-compilation c
  • 是的,该结构可以根据实现进行填充,因为有奇数个整数,然后是一个双精度数。这可能是一个对齐问题,具体取决于平台。

标签: c++ c gcc cuda nvcc


【解决方案1】:

不能保证两个不同的 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 */

可能有一种特定于编译器的方式来指定不同的对齐方式,但这种技术是not always safe

理想的解决方案是对 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;
};

那么您更有可能让xy 在两个编译器下具有相同的偏移量。您仍然需要进行试验以确保一切一致。

由于 C 和 C++ 代码将成为同一程序的一部分(对吗?),您不必担心字节顺序变化等问题。如果您想在单独的程序之间传输结构类型的值,例如通过将它们存储在文件中或通过网络传输它们,您可能需要定义一种一致的方式将结构值序列化为字节序列,反之亦然。

【讨论】:

    【解决方案2】:

    GPU 要求所有数据自然对齐,例如一个 4 字节的 int 需要与一个 4 字节的边界对齐,一个 8 字节的 double 或 long long 需要有 8 字节的对齐。 CUDA 也对主机代码强制执行此操作,以确保代码的主机和设备部分之间的结构尽可能兼容。另一方面,x86 CPU 通常不要求数据自然对齐(尽管缺乏对齐可能会导致性能下降)。

    在这种情况下,CUDA 需要将结构的 double 组件对齐到 8 字节边界。由于在 double 之前有奇数个 int 组件,因此这需要填充。切换组件的顺序,即将双组件放在首位,并没有帮助,因为在此类结构的数组中,每个结构都必须是 8 字节对齐的,因此结构的大小必须是 8 字节的倍数才能实现这一点,这也需要填充。

    要强制 gcc 以与 CUDA 相同的方式对齐双精度,请传递标志 -malign-double

    【讨论】:

      【解决方案3】:

      似乎 2 个编译器应用了不同的填充:一个使用 4 字节对齐,另一个使用至少 8 字节对齐。您应该能够通过特定于编译器的 #pragma 指令强制对齐所需的对齐(检查您的编译器文档以了解特定的 #pragma)。

      【讨论】:

      • #pragma pack (4)#pragma pack (8) 似乎没有帮助,它们会导致相同的错误。我如何为 gcc 执行此操作?
      • 我认为 gcc 需要的是编译器标志 -malign-double。由于 GPU 需要对所有数据进行自然对齐,因此 CUDA 也在主机上强制执行此操作,以确保代码的主机和设备部分之间的结构兼容。由于结构中双精度之前的整数数量是奇数,因此需要填充结构。或者,您可以重新排序结构的组件,以便双精度是第一个组件。
      • @njuffa 所以重新排序不起作用,但 -malign-double 确实起作用。您应该将其发布为答案,以便我给予您信任。谢谢!
      • 很高兴听到它奏效了。我已按照您的建议发布了答案。
      猜你喜欢
      • 2011-12-09
      • 1970-01-01
      • 2017-04-20
      • 1970-01-01
      • 2015-09-11
      • 1970-01-01
      • 2016-11-03
      • 2017-09-16
      • 1970-01-01
      相关资源
      最近更新 更多