使用预处理器计算奇偶校验位(通过ref调用奇偶校验功能样式)

时间:2012-07-02 13:14:43

标签: c c-preprocessor compile-time parity

考虑我想在编译时生成奇偶校验。奇偶校验计算给出了字面常量,并且对于任何体面的优化器,它将归结为单个常量本身。现在使用 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个实例,其本身可以是例如([0..7]*3+1)/4。任意复杂的表达。问题是 C 预处理器无法评估中间表达式,并且在一般情况下只扩展文本(您可以强制它在原位进行整数运算,但仅适用于琐碎的情况,或者千兆字节的#defines)。 我发现3位的奇偶校验可以通过算术表达式#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)) 一次生成。这会将16位奇偶校验减少到以下宏:

u16

只展开(x*k+d)/m 6次。是否有更便宜的(在扩展数量方面)方式,例如4,5的直接公式等。比特平价?对于范围>的可接受(非溢出)值k,d,m,我找不到形式{{1}}的线性表达式的解决方案。 3位。那里的任何人都有一个更聪明的预处理器奇偶校验计算快捷方式?

2 个答案:

答案 0 :(得分: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预处理器每次都计算它们), 和“#include”嵌入式系统的“.c”文件中的“.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”文件 如果且仅当有必要时。

答案 1 :(得分:-1)

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

如果你坚持使用宏,你可以定义一个临时变量 使用gcc,您可以执行此操作,并且仍然具有表现为表达式的宏:
#define PARITY(x) ({int tmp=x; PARITY16(tmp);})
如果你想坚持标准,你必须使宏成为一个声明:
#define PARITY(x, target) do { int tmp=x; target=PARITY16(tmp); } while(0)

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