代码之家  ›  专栏  ›  技术社区  ›  Christoffer

在C结构中自动重新排序字段以避免填充

  •  11
  • Christoffer  · 技术社区  · 15 年前

    我花了几分钟的时间手动重新排序结构中的字段,以减少填充效果[1],这让我觉得时间太长了。我的直觉告诉我,我的时间可能会更好地花在写一个Perl脚本上,或者不为我做这种优化。

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

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

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

    编辑2:另一个复杂问题:根据配置,某些数据类型的大小也可能发生变化。最明显的是指针和不同体系结构的指针差异,也包括浮点类型(16位、32位或64位取决于“精确性”)、校验和(8位或16位取决于“速度”)以及其他一些不明显的东西。

    [1]所讨论的结构在一个嵌入式设备上被实例化数千次,因此结构的每4字节减少可能意味着 不去 对于这个项目。

    [2]可用的编译器有GCC 3.*和4.*、Visual Studio、TCC、ARM ADS 1.2、RVCT 3.*以及其他一些更模糊的编译器。

    8 回复  |  直到 11 年前
        1
  •  6
  •   Charlie    15 年前

    如果您能从存储中挤出的每个单词都是关键的,那么我建议您手工优化结构。一个工具可以为您优化地安排成员,但是它不知道,例如,您存储在16位中的这个值实际上永远不会超过1024,所以您可以为 价值超过 在这里

    所以在这项工作中,人类几乎肯定会打败机器人。

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

    我确实认为这个问题不适合一般的解决方案,但是您可以将您的域知识编码成一个定制的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
    };
    
        2
  •  6
  •   sigjuice    12 年前

    Perl安装中通常包含一个名为pstruc的Perl脚本。脚本将转储结构成员偏移量和大小。您可以修改pstruc,也可以将其输出作为一个起点,用于生成一个实用程序,以您想要的方式打包您的结构。

    $ 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
    }
    
        3
  •  2
  •   Aaron Digulla    15 年前

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

    因此,最好的办法可能是将结构编写为伪代码,然后编写一个预处理器脚本,从中创建真正的结构。

        4
  •  0
  •   Howard May    15 年前

    看看pragma pack。这将更改编译器如何对齐结构中的元素。你可以用它来强迫他们紧密地包装在一起,没有空间。

    See more details here

        5
  •  0
  •   gbjbaanb    15 年前

    它也将依赖于平台/编译器。如前所述,大多数编译器将所有内容填充为4字节对齐(或更糟!),假设一个结构有两条短裤和一条长短裤:

    short
    long
    short
    

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

    重新排序为

    short
    short
    long
    

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

    至于要重新排序的工具,我只需(手动)重新组织结构布局,以便将不同类型放在一起。先把所有的短裤都穿好,然后把所有的长筒袜都穿上,等等。如果你想把包装做好,不管怎样,这就是工具所能做的。在类型之间的转换点中间可能有2个字节的填充,但我认为这不值得担心。

        6
  •  0
  •   Johan Kotlinski    15 年前

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

    在您编写时,当然完全有可能生成某种以有效方式在字段中来回移动的代码生成器。但我更喜欢手动操作。

        7
  •  0
  •   Dan Olson    15 年前

    想一想我该怎么做一个这样的工具…我想我先从调试信息开始。

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

    在你得到信息后,剩下的就很简单了。将成员从大到小排序,尽量保持原始结构的顺序。使用Perl或python,甚至可以很容易地将其与源代码的其余部分交叉引用,甚至可能保留注释或ifdef,这取决于它们使用的多么干净。最大的痛苦是更改整个代码库中结构的所有初始化。伊克斯。

    事情是这样的。听起来不错,但我不知道有这样的 现有的 这样做的工具,当你写你自己的时候…我认为您将能够手动重新排序程序中的大多数结构。

        8
  •  0
  •   Jérôme Pouiller    11 年前

    我也有同样的问题。正如另一个答案所建议的,pstruc可能会有所帮助。但是,它确实提供了我们所需要的。实际上pstruc使用gcc提供的调试信息。我根据同样的想法写了另一个剧本。

    必须使用存根调试信息生成程序集文件( -gstubs )。(可能从dwarf获得相同的信息,但我使用的方法与pstruc相同)。在不修改编译过程的情况下进行此操作的一个好方法是添加 "-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
    
    推荐文章