编写一个variadic宏,用于设置整数中的特定位(位掩码)

时间:2016-01-31 23:10:57

标签: c bit-manipulation c-preprocessor bitmask avr-gcc

我正在尝试编写一个宏,它简化了整数中多个位的设置。初始化配置寄存器时,这通常发生在微控制器代码中。例如,可以通过设置寄存器TCCR0A中的3位来配置8位定时器,如下所示:

// WGM01, WGM00 and COM0A1 are constants between 0 and 7
// There are hundreds of these constants defined in avr-libc
TCCR0A |= (1<<WGM01) | (1<<WGM00) | (1<<COM0A1);

// Another way to write this:
#define _BV(bit) (1 << (bit)) // <-- defined in avr-libc
TCCR0A |= _BV(WGM01) | _BV(WGM00) | _BV(COM0A1);

但是,我发现写这样的东西要容易得多:

TCCR0A |= BITS(WGM01, WGM00, COM0A1); // <- Variable # of arguments please!

由于我无法想象没有人想到这一点,所以我四处寻找,但没有发现任何事情。我想知道这是否可行,但在阅读https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlhttps://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms时,我还是试了一下。

这是我到目前为止所尝试的内容。我想解决方案必须是递归宏,但是在尝试使其正确扩展时并没有走得太远。由于我的所有寄存器都是8位长,因​​此8次扩展通道就足够了(第一次尝试)。

#define BITS_EVAL(...)  BITS_EVAL1(BITS_EVAL1(BITS_EVAL1(__VA_ARGS__)))
#define BITS_EVAL1(...) BITS_EVAL2(BITS_EVAL2(BITS_EVAL2(__VA_ARGS__)))
#define BITS_EVAL2(...) __VA_ARGS__

#define BITS(bit, ...) ((1 << bit) | BITS_EVAL(BITS(__VA_ARGS__)))

上述方法并不奏效。它目前的作用是:

// BITS(2,5,7) --> ((1 << 2) | BITS(5, 7))

但是,我想要实现的是其中之一(或等效的):

// BITS(2,5,7) --> ((1 << 2) | (1 << 5) | (1 << 7))
// BITS(2,5,7) --> ((1 << 2) | ((1 << 5) | ((1 << 7))))

任何人都可以帮助我完成我的任务,或者告诉我这是不可能的吗?

3 个答案:

答案 0 :(得分:4)

  

警告: 写这个主要是学习练习。

     

请勿在生产代码中使用。如果你这样做,人们会正确诅咒你。

所以,在使用Paul的answersgithub wiki中的宏进行了一些讨论之后,我实际上设法生成了一个有效的BITS(...)宏来实现我的意图。它是一个递归宏,可以多次扫描以扩展递归替换。它处理可变数量的参数,并支持最多64位的整数。

// test.c
#include "bits.h"
int a = BITS(1,5,7);
int b = BITS(3);
int c = BITS(); // This case is broken but irrelevant

使用gcc -E test.c -o test.txt扩展为:

int a = (0 | (1ull<<1) | (1ull<<5) | (1ull<<7));
int b = (0 | (1ull<<3));
int c = (0 | (1ull<<)); // This case is broken but irrelevant

开头的0 |是实现的工件,但显然不会影响表达式的结果。

这是实际的实施,包括评论:

// bits.h
// Macros partially from https://github.com/pfultz2/Cloak
#define EMPTY(...)
// Defers expansion of the argument by 1, 2 or 3 scans
#define DEFER(...) __VA_ARGS__ EMPTY()
#define DEFER2(...) __VA_ARGS__ DEFER(EMPTY)()
#define DEFER3(...) __VA_ARGS__ DEFER2(EMPTY)()

// Concatenate the arguments to one token
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

// Apply multiple scans to the argument expression (>64 to allow uint64_t masks)
#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) __VA_ARGS__

// Always expand to the second token after expansion of arguments.
// One level of indirection to expand arguments before selecting.
#define SELECT_2ND(...) SELECT_2ND_INDIRECT(__VA_ARGS__, , )
#define SELECT_2ND_INDIRECT(x1, x2, ...) x2

// Expands to a comma (which means two empty tokens in a parameter list).
// Thus, SELECT_2ND will expand to an empty token if this is the first argument.
#define BITS_RECURSIVE__END_RECURSION ,

// Adds the END_RECURSION parameter, which marks the end of the arguments
#define BITS(...) \
    (0 EVAL(BITS_RECURSIVE(__VA_ARGS__, END_RECURSION,)))

// When hitting END_RECURSION, the CAT will expand to "," and SELECT_2ND
// will select the empty argument instead of the recursive call.
#define BITS_RECURSIVE(bit, ...) \
    SELECT_2ND(PRIMITIVE_CAT(BITS_RECURSIVE__, bit), \
             | (1ull<<(bit)) DEFER3(BITS_INDIRECT)()(__VA_ARGS__))
// Needed to circumvent disabling contexts for recursive expansion
#define BITS_INDIRECT() BITS_RECURSIVE

和一些代码来测试极端情况:

// test2.c
#include "bits.h"
#include <inttypes.h>
#include <stdio.h>

uint8_t u8 = BITS(0,1,2,3,4,5,6,7);
uint32_t u32 = BITS(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
        16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31);
uint64_t u64 = BITS(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
        16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
        32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
        48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63);
uint64_t a64 = BITS(0,1,2,3,4,5,6,7,
        16,17,18,19,20,21,22,23,
        32,33,34,35,36,37,38,39,
        48,49,50,51,52,53,54,55);

int main(void) {
    printf("0x%02" PRIX8 "\n", u8);    // Prints 0xFF
    printf("0x%08" PRIX32 "\n", u32);  // Prints 0xFFFFFFFF
    printf("0x%016" PRIX64 "\n", u64); // Prints 0xFFFFFFFFFFFFFFFF
    printf("0x%016" PRIX64 "\n", a64); // Prints 0x00FF00FF00FF00FF
    return 0;
}

答案 1 :(得分:2)

GCC可变参数宏功能,打算在运行时将变量参数列表传递给类似于printf的函数,比如setBits(3,WGM01,WGM00,COM0A1)和OR n值。我怀疑在运行时评估它是可以接受的。

如果您真的想按上述方式编写BITS,我认为您可以使用宏处理器m4,它允许通过移动参数列表进行递归定义,因此您可以测试$#是1还是&gt; = 2。扩展将运作如下:

1 TCCR0A |= BITS( WGM00 , COM0A1 , WGM01 );
2 TCCR0A |= (1u << WGM00) | BITS( COM0A1 , WGM01 );          # (1u << $1) | BITS( shift($*))
3 TCCR0A |= (1u << WGM00) | (1u << COM0A1) | BITS( WGM01);      # Recusion terminates
4 TCCR0A |= (1u << WGM00) | (1u << COM0A1) | (1u << WGM01);

不知怎的,我不希望感谢在C源代码中包含类似的内容:

define(`BITS',`ifelse(eval($#<2),1, (1u<<`$1`''),
(1u<<`$1`'') | BITS(shift($@))')')

在CPP技巧中,显然可以分离第一个参数,但不支持递归宏,也没有评估终止条件的方法。也许一连串的扩展是可行和清晰的:

#define BITS4(m, ...) ((1u<<m) | BITS3(__VA_ARGS__))
#define BITS3(m, ...) ((1u<<m) | BITS2(__VA_ARGS__))
#define BITS2(m, ...) ((1u<<m) | BITS1(__VA_ARGS__))
#define BITS1(m) ( 1u << m)

测试:

printf( "BITS3( 0, 1, 2) %u\n", BITS3( 0,1, 2));
printf( "BITS2( 0, 1) %u\n", BITS2( 0,1));
printf( "BITS1( 0) %u\n", BITS1( 0));

结果:     BITS3(0,1,2)7     BITS2(0,1)3     BITS1(0)1

这是预期的。虽然这不是宏所希望的一般位集,但解决方案清晰,所以应该是可维护的。

答案 2 :(得分:1)

将位宏定义为它们设置的实际位,不需要幻数硬编码,因为如果宏发生变化,那么它们的_B对应物就会出现:

#define WGM00_B  (1u<<WGM00)
#define COM0A1_B (1u<<COM0A1)
#define WGM01_B  (1u<<WGM01) 
...

然后简单地或者他们在一起,不需要宏,顺序无关紧要:

TCCR0A |= WGM00_B | COM0A1_B | WGM01_B;

或者把它放在一个宏中。用法就像你问的那样,但是使用按位运算符代替逗号。

TCCR0A |= BITS( WGM00_B | COM0A1_B | WGM01_B );

宏BITS简单定义为:

#defined BITS( b ) (b)