【问题标题】:C sizeof equivalent for macros宏的等效 C sizeof
【发布时间】:2009-08-20 20:57:43
【问题描述】:

所以我通过展开一些循环来优化一些代码(是的,我知道我应该依靠我的编译器为我做这件事,但我没有使用我选择的编译器)并且我想这样做有点优雅,这样万一我的数据大小由于将来的一些编辑而发生变化,代码就会优雅地降级。

类似:

typedef struct {
    uint32_t alpha;
    uint32_t two;
    uint32_t iii;
} Entry;

/*...*/

uint8_t * bytes = (uint8_t *) entry;
#define PROCESS_ENTRY(i) bytes[i] ^= 1; /*...etc, etc, */ 
#if (sizeof(Entry) == 12)
    PROCESS_ENTRY( 0);PROCESS_ENTRY( 1);PROCESS_ENTRY( 2);
    PROCESS_ENTRY( 3);PROCESS_ENTRY( 4);PROCESS_ENTRY( 5);
    PROCESS_ENTRY( 6);PROCESS_ENTRY( 7);PROCESS_ENTRY( 8);
    PROCESS_ENTRY( 9);PROCESS_ENTRY(10);PROCESS_ENTRY(11);
#else
#   warning Using non-optimized code
    size_t i;
    for (i = 0; i < sizeof(Entry); i++)
    {
        PROCESS_ENTRY(i);
    }
#endif
#undef PROCESS_ENTRY

这当然行不通,因为sizeof 不适用于预处理器(至少,this answer 似乎表明了这一点)。

是否有一种简单的解决方法可以让sizeof 成为与 C 宏一起使用的数据结构,或者我只是 SOL?

【问题讨论】:

  • 好吧,sizeof() 一个宏。至少是一个内置宏。
  • sizeof 不是任何形状或形式的宏
  • sizeof 不是宏,尽管 offsetof 是。 sizeof 更像是一个运算符。
  • 顺便说一句,您确定展开循环是一种优化吗?您可能会减少 CPU 时间,或者您可能会强制更多缓存未命中。如果您需要代码执行得更快一点,则需要进行测量。如果您发现不需要展开,则不必经历所有这些。
  • 写出来的循环看起来很简单,上限是一个常量表达式。我不怀疑 GCC 是否会优化它。

标签: c sizeof c-preprocessor


【解决方案1】:

你不能在预处理器中这样做,但你不需要这样做。只需在您的宏中生成一个普通的if

#define PROCESS_ENTRY(i) bytes[i] ^= 1; /*...etc, etc, */ 
if (sizeof(Entry) == 12) {
    PROCESS_ENTRY( 0);PROCESS_ENTRY( 1);PROCESS_ENTRY( 2);
    PROCESS_ENTRY( 3);PROCESS_ENTRY( 4);PROCESS_ENTRY( 5);
    PROCESS_ENTRY( 6);PROCESS_ENTRY( 7);PROCESS_ENTRY( 8);
    PROCESS_ENTRY( 9);PROCESS_ENTRY(10);PROCESS_ENTRY(11);
} else {
    size_t i;
    for (i = 0; i < sizeof(Entry); i++) {
        PROCESS_ENTRY(i);
    }
}

sizeof 是一个常量表达式,将常量与常量进行比较也是常量。任何理智的 C 编译器都会优化掉在编译时总是错误的分支——常量折叠是最基本的优化之一。但是,您丢失了#warning

【讨论】:

  • 从稍微不同的角度来看问题总是好的。 +1 和荣誉!
【解决方案2】:

如果您使用autoconf 或其他构建配置系统,您可以在配置时检查数据结构的大小并写出标题(如#define SIZEOF_Entry 12)。当然,在交叉编译等情况下,这会变得更加复杂,但我假设您的构建和目标架构是相同的。

否则,你就不走运了。

【讨论】:

    【解决方案3】:

    你不走运 - 预处理器甚至不知道结构是什么,更不用说任何计算其大小的方法了。

    在这种情况下,您可以#define 一个常量来表示您碰巧知道结构的大小,然后使用负大小数组技巧静态断言它实际上等于大小。

    你也可以试试if (sizeof(Entry) == 12),看看你的编译器是否能够在编译时评估分支条件并删除死代码。问题不大。

    【讨论】:

    • 使用 assert() 而不是 #warning 的想法也是一个好主意。您真的想要优雅的降级,还是想要清楚地了解问题以便解决问题?
    • 它不能是 12 的原因有两个。一个是有一些意外的填充(在这种情况下你可能想要一个错误,所以你可以使用编译器特定的编译指示来打包结构),一个是您添加了一个字段(在这种情况下,您想要更新 #define 以使其代表新大小,然后还要决定是否更新展开的循环以处理新大小)。所以实际上,我会在结构的定义点有断言,并且 在展开点有警告。
    • 但我应该补充一点,我永远不会允许警告存在,至少不会在我自己的代码中,所以对我来说,警告意味着“这是一个错误,如果绝对必要,你可以暂时忽略只是为了编译其余的代码”。
    【解决方案4】:

    我想到了另外两种方法 - 要么编写一个小应用程序来编写展开的循环,要么使用 Duff's device 的变体和预期的结构大小。

    【讨论】:

    • 任何生成代码比 Duff 的设备慢的现代编译器都不是一个很好的编译器。
    • 这不取决于它对代码大小的偏执程度吗?编译器不一定为了好玩而将代码大小乘以 12。如果没有分析器输入,优化器就无法知道哪些循环值得支付代码大小以提高速度。展开所有内容会导致大量代码、大量 icache 未命中和速度下降。另一方面,您可以智能地选择要展开的循环(或者更智能地,使用可以使用分析器数据进行优化的编译器)。除非你的意思是达夫的设备现在已经过时了,因为我不知道有更新、更好的技巧。
    【解决方案5】:

    这可能无济于事,但如果您有能力在 C++ 中执行此操作,则可以使用模板使编译器在编译时分派到适当的循环:

    template <std::size_t SizeOfEntry>
    void process_entry_loop(...)
    {
        // ... the nonoptimized version of the loop
    }
    
    template <>
    void process_entry_loop<12>(...)
    {
        // ... the optimized version of the loop
    }
    
    // ...
    
    process_entry_loop<sizeof(Entry)>(...);
    

    【讨论】:

    • 好主意,但是将 sizeof(Entry) 设置为模板参数会更清楚,并专门针对 12。
    【解决方案6】:

    如果您希望结构体的大小尽可能小(或将其对齐到 4 字节边界,或其他),您可以使用打包或对齐属性。

    在 Visual C++ 中,您可以使用 #pragma pack,而在 GCC 中,您可以使用 __attribute__((packed)) 和 __attribute__((aligned(num-bytes))。

    【讨论】:

    • 那不会给出大小。此外,虽然打包会节省空间,但可能会花费时间,而这正是他想要获得的。
    • @David Thornley:它不会给出大小,但它会做他想要在这个代码块中做的事情。通过打包结构,他将确定填充不会妨碍,因此结构 正好 12 个字节(或每个项目大小的总和),因此没有 需要 使用预处理器宏来查找结构的大小。 PROCESS_ENTRY 对未填充的结构使用字节级访问,因此打包结构可以使用此宏而无需担心填充。
    • 我应该补充一点,通常最好不要干预,让优化器完成它的工作(特别是因为它比大多数程序员都做得更好)。
    猜你喜欢
    • 2011-03-20
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多