【问题标题】:Calculating parity bit with the preprocessor (parity functional style with call by ref)使用预处理器计算奇偶校验位(通过 ref 调用奇偶校验功能样式)
【发布时间】:2012-07-02 21:55:54
【问题描述】:

考虑我想在编译时生成奇偶校验。奇偶校验计算给出了文字常量,并且使用任何体面的优化器,它本身都会归结为一个常量。现在用 C 预处理器查看以下奇偶校验计算:

#define PARITY16(u16) (PARITY8((u16)&0xff) ^ PARITY8((u16)>>8))
#define PARITY8(u8) (PARITY4((u8)&0x0f) ^ PARITY4((u8)>>4))
#define PARITY4(u4) (PARITY2((u4)&0x03) ^ PARITY2((u4)>>2))
#define PARITY2(u2) (PARITY1((u2)&0x01) ^ PARITY1((u2)>>1))
#define PARITY1(u1) (u1)

int message[] = { 0x1234, 0x5678, PARITY16(0x1234^0x5678));

这将在编译时计算奇偶校验,但它会产生大量的中间代码,扩展为表达式 u16 的 16 个实例,它本身可以是例如任意复杂的表达式。问题是 C 预处理器无法评估中间表达式,并且在一般情况下只能扩展文本(您可以强制它在原地进行整数运算,但仅适用于微不足道的情况,或者千兆字节#定义)。 我发现 3 位的奇偶校验可以通过算术表达式一次生成:([0..7]*3+1)/4。这会将 16 位奇偶校验减少为以下宏:

#define PARITY16(u16) ((4 & ((((u16)&7)*3+1) ^           \
                            ((((u16)>>3)&7)*3+1) ^               \
                            ((((u16)>>6)&7)*3+1) ^               \
                            ((((u16)>>9)&7)*3+1) ^               \
                            ((((u16)>>12)&7)*3+1) ^              \
                            ((((u16)>>15)&1)*3+1))) >> 2))

u16仅扩展 6 次。是否有更便宜(就扩展数量而言)的方式,例如4,5等的直接公式位平价?对于范围> 3位的可接受(非溢出)值k,d,m,我找不到(x*k+d)/m形式的线性表达式的解决方案。有谁有更聪明的预处理奇偶校验计算捷径?

【问题讨论】:

  • 不要使用宏,使用内联函数。这提供了您正在寻找的中间表达式评估。
  • 我必须同意 mfontanini 的观点,这里最好避免使用宏。如果您使用的是 c++11,则可以使用 constexpr 方法。如果您不使用 c++11 并且需要将结果作为编译时常量(不仅可能在编译时评估,而且可用作常量),请使用模板元编程在编译时评估它,否则只需使用内联函数.如果您使用纯 C,您可能希望从标签中删除 C++。
  • 对不起,我也用 C++ 标记了它,不想排除他们 V8-autodrive-endless-horsepower 家伙 ;)。唉,这应该在纯 C89 环境中执行
  • C 和 C++ 是不同的语言。如果您需要一个仅适用于 C 的答案,则不应将其标记为 C++。
  • 你不需要那么多&。你可以改用这个:#define PARITY16(x) PARITY8((x) ^ ((x) >> 8))#define PARITY8(x) PARITY4((x) ^ ((x) >> 4))#define PARITY4(x) PARITY2((x) ^ ((x) >> 2))#define PARITY2(x) (((x) ^ ((x) >> 1)) & 1)

标签: c c-preprocessor compile-time parity


【解决方案1】:

您正在寻找这样的东西吗? 以下“PARITY16(u16)”预处理宏可用作结构赋值中的文字常量,并且它只对参数求值一次。

/* parity.c
 * test code to test out bit-twiddling cleverness
 * 2013-05-12: David Cary started.
*/

// works for all 0...0xFFFF
// and only evalutes u16 one time.
#define PARITYodd33(u33) \
    ( \
        ((((((((((((((( \
            (u33) \
        &0x555555555)*5)>>2) \
        &0x111111111)*0x11)>>4) \
        &0x101010101)*0x101)>>8) \
        &0x100010001)*0x10001)>>16) \
        &0x100000001)*0x100000001)>>32) \
    &1)
#define PARITY16(u16) PARITYodd33(((unsigned long long)u16)*0x20001)

// works for all 0...0xFFFF
// but, alas, generates 16 instances of u16.
#define PARITY_16(u16) (PARITY8((u16)&0xff) ^ PARITY8((u16)>>8))
#define PARITY8(u8) (PARITY4((u8)&0x0f) ^ PARITY4((u8)>>4))
#define PARITY4(u4) (PARITY2((u4)&0x03) ^ PARITY2((u4)>>2))
#define PARITY2(u2) (PARITY1((u2)&0x01) ^ PARITY1((u2)>>1))
#define PARITY1(u1) (u1)

int message1[] = { 0x1234, 0x5678, PARITY16(0x1234^0x5678) };
int message2[] = { 0x1234, 0x5678, PARITY_16(0x1234^0x5678) };

#include <stdio.h>

int main(void){
        int errors = 0;
        int i=0;
        printf(" Testing parity ...\n");
        printf(" 0x%x = message with PARITY16\n", message1[2] );
        printf(" 0x%x = message with PARITY_16\n", message2[2] );
        for(i=0; i<0x10000; i++){
                int left = PARITY_16(i);
                int right =  PARITY16(i);
                if( left != right ){
                        printf(" 0x%x: (%d != %d)\n", i, left, right );
                        errors++;
                        return 0;
                };
        };
        printf(" 0x%x errors detected. \n", errors );
} /* vim: set shiftwidth=4 expandtab ignorecase : */

与您发布的原始代码非常相似,它将位配对并(实际上)计算每对之间的 XOR,然后根据结果再次配对位,每次将位数减半,直到只有一个奇偶校验有点残留。

但这真的是你想要的吗?

许多人他们正在计算消息的“奇偶性”。 但根据我的经验,大多数时候它们确实在生成 error-detection code 大于单个奇偶校验位—— LRC,或CRC,或Hamming code,或等等。

更多细节

如果当前系统在合理的时间内编译, 它给出了正确的答案,我会不理会它。 重构“预处理器如何生成一些常量” 将逐位生成相同的运行时可执行文件。 我宁愿有易于阅读的源代码 即使编译需要一秒钟的时间。

许多人使用比标准 C 预处理器更易于阅读的语言来生成 C 源代码。 见pycrccharacter set extractor"using Python to generate C"

如果当前系统编译时间过长, 而不是调整 C 预处理器, 我很想将该消息(包括奇偶校验)放在单独的“.h”文件中 使用硬编码的常量(而不是强制 C 预处理器每次都计算它们), 并在嵌入式系统的“.c”文件中“#include”那个“.h”文件。

然后我会制作一个完全独立的程序(可能在 C 或 Python 中) 进行奇偶校验计算和 将该“.h”文件的内容打印为预先计算的 C 源代码, 像

print("int message[] = { 0x%x, 0x%x, 0x%x };\n",
    M[0], M[1], parity( M[0]^M[1] ) );

并调整我的MAKEFILE 以运行该 Python(或其他)程序以重新生成该“.h”文件 当且仅当,这是必要的。

【讨论】:

  • 感谢您的详尽回答和您的额外想法。我完全同意你的观点,但我之所以采用纯 C 预处理是有原因的:代码进入了一个配置管理非常严格的现有项目,因此不能选择引入外部代码生成语言。顺便说一句:我的算术快捷方式也可以并行推广到更多位:#define PARITY_24(x) \ ((((((((x)&070707070u)*3+010101010)&040404040) + \ ((((x))) &007070707u)*3+001010101)&004040404)) * 0111111110) / 0400000000) & 1)
【解决方案2】:

正如 mfontanini 所说,内联函数要好得多。

如果你坚持使用宏,你可以定义一个临时变量。
使用gcc,您可以做到这一点,并且仍然拥有充当表达式的宏:
#define PARITY(x) ({int tmp=x; PARITY16(tmp);})
如果你想坚持标准,你必须让宏声明:
#define PARITY(x, target) do { int tmp=x; target=PARITY16(tmp); } while(0)

在这两种情况下,如果 tmp 以函数中使用的名称结尾(更糟糕的是 - 在传递给宏的参数中使用),您可能会遇到丑陋的错误。

【讨论】:

  • 抱歉,您错过了我的主要要求:奇偶校验必须在编译时进行评估。
  • 为什么我错过了?参数为常量的宏(和内联函数)在编译时求值。
  • 更具体地说,我希望它评估为文字常量,以便能够在“int message[] = { 0x1234, 0x5678, PARITY16(0x1234^0x5678));”中使用它
  • 如果message 在函数内部,则没有问题。但是在函数之外,我的建议确实行不通。
猜你喜欢
  • 2013-06-25
  • 2015-06-29
  • 2017-06-22
  • 1970-01-01
  • 2015-01-10
  • 1970-01-01
  • 2015-04-04
  • 2020-04-20
相关资源
最近更新 更多