【问题标题】:What is the easiest way to find the sizeof a type without compiling and executing code?在不编译和执行代码的情况下找到类型大小的最简单方法是什么?
【发布时间】:2021-11-19 07:14:24
【问题描述】:

我编写了一个 bash 脚本来确定 gcc 的数据类型的大小(例如,./sizeof int double 输出 intdouble 的各自大小),方法是将其每个参数包装在以下 P() 宏中然后编译并运行代码。

#define P(x) printf("sizeof(" #x ") = %u\n", (unsigned int)sizeof(x))

问题是这相对较慢(需要一整秒!),尤其是链接步骤(因为使用-c-S 编译几乎不需要时间,运行输出的二进制文件也是如此)。一秒钟本身并没有那么慢,但是如果我在其他脚本中使用这个脚本,它会加起来。

是否有更快、更少迂回的方法来找出 gcc 用于数据类型的大小?

【问题讨论】:

  • 只是出于好奇,当您还没有编写和编译 C 代码时,什么时候需要知道 C 类型的大小?
  • 运行一次并输出到一个文件,当你需要知道的时候读取那个文件

标签: c bash gcc sizeof


【解决方案1】:

您只能使用 GCC 的预处理器来实现标准类型的功能。对于标准类型,有预定义的宏:

__SIZEOF_INT__
__SIZEOF_LONG__
__SIZEOF_LONG_LONG__
__SIZEOF_SHORT__
__SIZEOF_POINTER__
__SIZEOF_FLOAT__
__SIZEOF_DOUBLE__
__SIZEOF_LONG_DOUBLE__
__SIZEOF_SIZE_T__
__SIZEOF_WCHAR_T__
__SIZEOF_WINT_T__
__SIZEOF_PTRDIFF_T__

所以,通过使用如下代码:

#define TYPE_TO_CHECK __SIZEOF_INT__
#define VAL_TO_STRING(x) #x
#define V_TO_S(x) VAL_TO_STRING(x)
#pragma message V_TO_S(TYPE_TO_CHECK)
#error "terminate"

您将能够从预处理器本身获取__SIZEOF_INT__ 的值,甚至无需开始编译。在您的脚本中,您可以将TYPE_TO_CHECK(带有-D)定义为您需要的任何内容并将其传递给gcc。当然你会得到一些垃圾输出,但我相信你可以处理。

【讨论】:

  • 我喜欢这个主意,尽管你可以这样做 echo __SIZEOF_INT__ | cpp | tail -1
  • 这将无法计算任意结构的大小
  • @EugeneSh.:可能只是你对填充的效果很好奇,不是吗?也许你想估计一些算法的内存消耗?谁知道呢。
  • @Wintermute "cpp" 不能保证与 gcc 使用的预处理器相同......在某些系统上它不是并且可能会产生不同的结果。您可能想要“gcc -E -”代替“cpp”
  • 你可以做到,其实也没那么复杂。 sizeof 是一个编译时操作符,所以如果你在你的文件中放入类似volatile const s = sizeof(struct arbStruct); 的东西,在汇编文件中你会得到类似的东西:_s: .long 16
【解决方案2】:

您可以使用autoconf(参见:AC_COMPUTE_INT)使用的“负数组大小”技巧。这样,您无需链接或执行代码。因此,它在交叉编译时也有效。例如,

int n[1 - 2 * !(sizeof(double) == 8)];

在以下情况下编译失败:sizeof(double) != 8

缺点是,您可能必须在命令行中传递-DCHECK_SIZE=8 或类似的东西,因为它可能需要多次传递才能检测到异常值。所以,我不确定这是否会更快总的来说 - 但你也许可以利用它。

编辑:如果您只使用 gcc,我认为@wintermute 的评论可能是最好的解决方案。

【讨论】:

  • 这里不输出大小,只是检查是否为8。
  • @Matt - 由您决定提供一系列值 - AC_COMPUTE_INT 本身实现了二进制搜索以收敛到正确的值。
【解决方案3】:

这里有三种可能的解决方案。

第一个适用于大小小于 256 的任何类型。在我的系统上,它大约需要 0.04 秒(因为它不需要除基本运行时之外的头文件或库)。一个缺点是它一次只能做一个,因为输出通道的尺寸很小。另一个问题是它无法弥补某些系统(尤其是 MinGW)上的慢速链接:

howbig() {
  gcc -x c - <<<'int main() { return sizeof ('$*'); }' && ./a.out
  echo $?
}

$ time howbig "struct { char c; union { double d; int i[3];};}" 
24

real    0m0.041s
user    0m0.031s
sys     0m0.014s

$ time howbig unsigned long long
8

real    0m0.044s
user    0m0.035s
sys     0m0.009s

如果您希望能够处理更大的类型,您可以一次获得一个字节的大小,代价是多几厘秒:

howbig2 () 
{ 
    gcc -x c - <<< 'int main(int c,char**v) {
                      return sizeof ('$*')>>(8*(**++v&3)); }' &&
    echo $((0x$(printf %02x $(./a.out 3;echo $?) $(./a.out 2;echo $?) \
                            $(./a.out 1;echo $?) $(./a.out 0;echo $?)) ))
}

$ time howbig2 struct '{double d; long long u[12];}([973])'
101192

real    0m0.054s
user    0m0.036s
sys     0m0.019s

如果您正在为 x86 进行编译,以下可能会起作用,尽管我无法在各种架构和平台上彻底测试它。通过分析编译的程序集输出,它避免了链接步骤(例如,在 MinGW 上非常慢)。 (分析编译的对象二进制文件可能会稍微健壮一些,但我担心 MinGW 上的 binutils 也很慢。)即使在 Ubuntu 上,它也明显更快:

howbig3 () { 
  gcc -S -o - -x c - <<< 'int hb(void) { return sizeof ('$*'); }' |
  awk '$1~/movl/&&$3=="%eax"{print substr($2,2,length($2)-2)}'
}

$ time howbig3 struct '{double d; long long u[12];}([973])'
101192

real    0m0.020s
user    0m0.017s
sys     0m0.004s

【讨论】:

  • 对我来说,这并不比我做的快,因为它并没有消除链接步骤。
  • 如果不清楚,瓶颈不是与标准库链接,而是链接器本身的调用(至少在我的系统上)。一个简单的int main() { return 0; } 程序需要同样长的链接时间。
  • @Matt 很有趣。你有什么系统?我可以直接从未链接的目标文件中提取返回值,但它会有些脆弱。
  • 在这种情况下使用 cygwin 的 Windows。
  • @Matt:MinGW 的链接速度是出了名的慢。我添加了一个分析程序集输出的解决方案,但我不知道它有多强大。祝你好运。
【解决方案4】:

使用nm 无代码

只要让你的东西成为一个全局变量。 nm 可以报告它的大小。

// getsize.c

struct foo {
    char str[3];
    short s;     // expect padding galore...
    int i;
} my_struct;

编译但不链接,然后使用nm:

$ gcc -c getsize.c
$ nm getsize.o --format=posix
my_struct C 000000000000000c 000000000000000c

请注意,最后一列是大小(十六进制),我们可以通过以下方式获取它:

$ nm test.o -P | cut -d ' ' -f 4
000000000000000c

# or in decimal
$ printf %d 0x`nm test.o -P | cut -d ' ' -f 4`
12

 

使用objdump 无代码

如果nm 因某种原因不起作用,您可以将size 本身 存储在全局变量中。

从这个 C 文件开始:

// getsize.c
struct foo { char str[3]; short s; int i; };

unsigned long my_sizeof = sizeof(struct foo);

现在我们必须从目标文件中找到这个变量的值。

$ gcc -c sizeof.c
$ objdump -Sj .data sizeof.o

test.o:     file format elf64-x86-64


Disassembly of section .data:

0000000000000000 <my_sizeof>:
   0:   0c 00 00 00 00 00 00 00                             ........

该死的,小端!您可以编写一个脚本来解析它,但以下解决方案(假设 GCC 扩展)将强制它始终为大端:

// getsize.c
struct foo { char str[3]; short s; int i; };

struct __attribute__ ((scalar_storage_order("big-endian"))) {
    unsigned long v;
} my_sizeof = { sizeof(struct foo) };

这会产生:

0000000000000000 <my_sizeof>:
   0:   00 00 00 00 00 00 00 0c                             ........

小心!您不能只删除所有非十六进制字符,因为有时右侧的“....”内容将是有效的 ASCII。但第一个应该始终是.。以下命令在: 和第一个. 之间保留内容。

$ gcc -c sizeof.c
$ objdump -Sj .data sizeof.o |

        sed '$!d                     # keep last line only
             s/\s//g                 # remove tabs and spaces
             s/.*:\([^.]*\)\..*/\1/' # only keep between : and .'

000000000000000c

【讨论】:

    【解决方案5】:

    如果你碰巧在 VS2019 之类的 IDE 中,只需在代码中的任意位置输入 char foo[sizeof(MyType)],将鼠标悬停在 foo 上即可获得答案:)

    【讨论】:

      猜你喜欢
      • 2011-03-21
      • 2018-03-13
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多