12

我花了几分钟手动重新排序结构中的字段以减少填充效果[1],这感觉就像几分钟太多了。我的直觉告诉我,我最好把时间花在编写 Perl 脚本之类的东西上来为我做这种优化。

我的问题是这是否也是多余的;是否已经有一些我不知道的工具,或者我应该能够打开[2] 来打包结构的一些编译器功能?

这个问题更加复杂,因为这需要在几个不同的架构上进行一致的优化,所以无论使用什么工具都需要能够考虑不同的结构对齐和指针大小。

编辑:快速澄清——我想要做的是重新排序源代码中的字段以避免填充,而不是像在没有填充的情况下编译那样“打包”结构。

编辑#2:另一个复杂情况:根据配置,某些数据类型的大小也可能发生变化。显而易见的是针对不同架构的指针和指针差异,还有浮点类型(16、32 或 64 位,取决于“精确度”)、校验和(8 位或 16 位,取决于“速度”)和一些其他不明显的东西。

[1] 有问题的结构在嵌入式设备上被实例化了数千次,因此结构每减少 4 字节可能意味着该项目的成功与否之间差异。

[2] 可用的编译器有 GCC 3.* 和 4.*、Visual Studio、TCC、ARM ADS 1.2、RVCT 3.* 和其他一些比较晦涩的编译器。

4

8 回答 8

7

如果您可以从存储中挤出的每个单词都很关键,那么我必须建议手动优化结构。一个工具可以为您最佳地安排成员,但它不知道,例如,您以 16 位存储的这个值实际上永远不会超过 1024,因此您可以窃取值的高 6 位这里...

因此,人类几乎肯定会在这项工作上击败机器人。

[编辑] 但似乎你真的不想为每个架构手动优化你的结构。也许你真的有很多架构需要支持?

我确实认为这个问题不适用于通用解决方案,但您可能能够将您的领域知识编码到自定义 Perl/Python/something 脚本中,该脚本会为每个架构生成结构定义。

此外,如果您的所有成员的大小都是 2 的幂,那么您只需按大小对成员进行排序(最大的优先)即可获得最佳包装。在这种情况下,您可以使用良好的老式基于宏的结构构建 -像这样的东西:

#define MYSTRUCT_POINTERS      \
    Something*  m_pSomeThing;  \
    OtherThing* m_pOtherThing; 

#define MYSTRUCT_FLOATS        \
    FLOAT m_aFloat;            \
    FLOAT m_bFloat;

#if 64_BIT_POINTERS && 64_BIT_FLOATS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS MYSTRUCT_FLOATS
#else if 64_BIT_POINTERS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS
#else if 64_BIT_FLOATS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_FLOATS
#else
    #define MYSTRUCT_64_BIT_MEMBERS
#endif

// blah blah blah

struct MyStruct
{
    MYSTRUCT_64_BIT_MEMBERS
    MYSTRUCT_32_BIT_MEMBERS
    MYSTRUCT_16_BIT_MEMBERS
    MYSTRUCT_8_BIT_MEMBERS
};
于 2009-05-15T08:16:10.463 回答
6

有一个称为 pstruct 的 Perl 脚本,它通常包含在 Perl 安装中。该脚本将转储结构成员的偏移量和大小。您可以修改 pstruct 或将其输出用作创建实用程序的起点,该实用程序可以按照您想要的方式打包您的结构。

$ cat foo.h 
struct foo {
    int x;
    char y; 
    int b[5];
    char c;
};

$ pstruct foo.h
struct foo {
  int                foo.x                      0       4
  char               foo.y                      4       1
                     foo.b                      8      20
  char               foo.c                     28       1
}
于 2009-05-15T08:26:22.153 回答
3

大多数 C 编译器不会这样做,因为您可以做一些奇怪的事情(比如获取结构中元素的地址,然后使用指针魔法访问其余部分,绕过编译器)。一个著名的例子是 AmigaOS 中的双链表,它使用守护节点作为列表的头部和尾部(这使得在遍历列表时可以避免 ifs)。守护者头节点将始终具有pred == null,尾节点将具有next == null,开发人员将这两个节点滚动到单个三指针结构head_next null tail_pred中。通过使用head_nextnull作为头尾节点的地址,他们节省了四个字节和一个内存分配(因为他们只需要整个结构一次)。

所以你最好的选择可能是将结构编写为伪代码,然后编写一个预处理器脚本,从中创建真正的结构。

于 2009-05-15T08:19:42.763 回答
0

看看#pragma pack。这改变了编译器对齐结构中元素的方式。您可以使用它来强制它们紧密排列在一起,没有空格。

在此处查看更多详细信息

于 2009-05-15T08:06:49.190 回答
0

它也取决于平台/编译器。如前所述,大多数编译器将所有内容都填充为 4 字节对齐(或更糟!),因此假设结构有 2 个 short 和一个 long:

short
long
short

将占用 12 个字节(带有 2*2 字节的填充)。

将其重新排序为

short
short
long

仍将占用 12 个字节,因为编译器将填充它以使数据访问更快(这是大多数桌面的默认设置,因为他们更喜欢快速访问而不是内存使用)。您的嵌入式系统有不同的需求,因此无论如何您都必须使用#pragma 包。

至于重新排序的工具,我会简单地(手动)重新组织您的结构布局,以便将不同的类型放在一起。先把所有的短裤放进去,然后把所有的长头放进去,等等。如果你要完成包装,无论如何这就是工具会做的事情。您可能在类型之间的转换点的中间有 2 个字节的填充,但我认为这不值得担心。

于 2009-05-15T08:30:35.140 回答
0

编译器可能不会通过自己的头部重新排序结构中的字段。标准要求字段应按定义的顺序排列。做其他事情可能会以微妙的方式破坏代码。

正如你所写的,当然完全有可能制作某种代码生成器,以一种有效的方式在字段周围打乱。但我更喜欢手动执行此操作。

于 2009-05-15T10:15:28.367 回答
0

考虑如何制作这样的工具......我想我会从调试信息开始。

从源头获取每个结构的大小是一件很痛苦的事情。它与编译器已经完成的许多工作重叠。我对 ELF 不够熟悉,无法准确说明如何从调试二进制文件中提取结构大小信息,但我知道该信息存在,因为调试器可以显示它。也许 objdump 或 binutils 包中的其他东西可以轻松地为您提供这个(至少对于使用 ELF 的平台)。

得到信息后,剩下的就很简单了。将成员从大到小排序,尽量保持原始结构的顺序。使用 perl 或 python 甚至可以很容易地将其与源代码的其余部分进行交叉引用,甚至可以根据使用的干净程度保留注释或 #ifdefs。最大的痛苦将是更改整个代码库中结构的所有初始化。哎呀。

事情就是这样。听起来很不错,但我不知道有任何这样的现有工具可以做到这一点,并且当你编写自己的......我认为你将能够手动重新排序程序中的大多数结构。

于 2009-05-15T10:40:50.147 回答
0

我有同样的问题。正如另一个答案中所建议的那样, pstruct 可能会有所帮助。但是,它确实提供了我们需要的东西。实际上 pstruct 使用 gcc 提供的调试信息。我基于相同的想法编写了另一个脚本。

您必须生成带有 STUBS 调试信息的程序集文件 ( -gstubs)。(可以从 dwarf 获得相同的信息,但我使用的方法与 pstruct 相同)。在不修改编译过程的情况下执行此操作的一个好方法是添加"-gstubs -save-temps=obj"到您的编译选项中。

以下脚本读取汇编文件并检测何时在结构中添加了额外的字节:

    #!/usr/bin/perl -n

    if (/.stabs[\t ]*"([^:]*):T[()0-9,]*=s([0-9]*)(.*),128,0,0,0/) {
       my $struct_name = $1;
       my $struct_size = $2;
       my $desc = $3;
       # Remove unused information from input
       $desc =~ s/=ar\([0-9,]*\);[0-9]*;[-0-9]*;\([-0-9,]*\)//g;
       $desc =~ s/=[a-zA-Z_0-9]+://g;
       $desc =~ s/=[\*f]?\([0-9,]*\)//g;
       $desc =~ s/:\([0-9,]*\)*//g;
       my @members = split /;/, $desc;
       my ($prev_size, $prev_offset, $prev_name) = (0, 0, "");
       for $i (@members) {
          my ($name, $offset, $size) = split /,/, $i;
          my $correct_offset = $prev_offset + $prev_size;
          if ($correct_offset < $offset) {
             my $diff = ($offset - $correct_offset) / 8;
             print "$struct_name.$name looks misplaced: $prev_offset + $prev_size = $correct_offset < $offset (diff = $diff bytes)\n";
          }
          # Skip static members
          if ($offset != 0 || $size != 0) {
            ($prev_name, $prev_offset, $prev_size) = ($name, $offset, $size);
          }
       }
    }

调用它的好方法:

find . -name *.s | xargs ./detectPaddedStructs.pl | sort | un
于 2013-08-21T14:21:19.387 回答