编写一个变量宏,它在一个整数中设置特定位(位掩码)
Writing a variadic macro which sets specific bits in an integer (bit-mask)
我正在尝试编写一个宏来简化整数中多个位的设置。这通常发生在初始化配置寄存器时的微控制器代码中。例如,可以通过在寄存器 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.html and https://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))))
任何人都可以帮助我完成我的任务,或者告诉我这是不可能实现的吗?
将您的位宏定义为它们设置的实际位,不需要幻数硬编码,因为如果宏发生变化,它们的 _B 对应物也会发生变化:
#define WGM00_B (1u<<WGM00)
#define COM0A1_B (1u<<COM0A1)
#define WGM01_B (1u<<WGM01)
...
然后简单地或它们在一起,不需要宏,顺序无关紧要:
TCCR0A |= WGM00_B | COM0A1_B | WGM01_B;
或者放在宏里。用法就像你问的那样,但你使用按位 or operator.
而不是逗号
TCCR0A |= BITS( WGM00_B | COM0A1_B | WGM01_B );
宏 BITS 简单定义为:
#defined BITS( b ) (b)
GCC 可变参数宏功能旨在在运行时将可变参数列表传递给类似于 printf 的函数,例如将 setBits( 3, WGM01, WGM00, COM0A1) 与 OR n 值一起。我怀疑在运行时对此进行评估是否可以接受。
如果你真的想像上面那样写 BITS,我想你可以使用宏处理器 m4,它允许递归定义并移动参数列表,所以你可以测试 $# 是 1 还是 >= 2。扩展会像这样运行:
1 TCCR0A |= BITS( WGM00 , COM0A1 , WGM01 );
2 TCCR0A |= (1u << WGM00) | BITS( COM0A1 , WGM01 ); # (1u << ) | 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<<``''),
(1u<<``'') | 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));
结果:
位 3( 0, 1, 2) 7
位 2( 0, 1) 3
BITS1( 0) 1
这是预期的。虽然这不是所希望的通用位设置宏,但解决方案很清晰,因此应该是可维护的。
Warning: Writing this was mostly a learning exercise.
DO NOT USE IN PRODUCTION CODE. People will rightly curse at you if you do.
因此,在进一步使用 Paul 的 answers and github 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;
}
几年后我找不到一个好的答案,所以我想到了这个...
- 一个
_bits
宏,它接受 8 个位位置参数(并接受但忽略任意数量的附加 ...
参数)。
- 一个
bits(...)
宏,将其参数连同 8 x 尾随 8
一起传递给 _bits
。
8
的结果为 (uint8_t)(1 << 8) == 0
(或者如果它们属于 _bits
的参数列表的 ...
部分则被忽略)。
注意 1. 被忽略的参数 x
用于支持空 args 的情况,bits() == 0
,这对于一致性来说似乎很好。例如你可能有空 #define OPTION_NO_FOO_DISABLED
并想要 bits(OPTION_NO_FOO) == 0
注2.只使用前8个参数。
例如bits(1,1,1,1,1,1,1,1,2) == 1
。 -- 谢谢弗里茨
#define _bits(x, i, j, k, l, m, n, o, p, ...) \
((uint8_t)( (uint8_t)(1U << (i)) | \
(uint8_t)(1U << (j)) | \
(uint8_t)(1U << (k)) | \
(uint8_t)(1U << (l)) | \
(uint8_t)(1U << (m)) | \
(uint8_t)(1U << (n)) | \
(uint8_t)(1U << (o)) | \
(uint8_t)(1U << (p)) ) )
#define bits(...) _bits(x, ## __VA_ARGS__, 8, 8, 8, 8, 8, 8, 8, 8)
int main()
{
assert(bits() == 0);
assert(bits(8) == 0);
assert(bits(7) == (uint8_t)((1 << 7)));
assert(bits(0) == (uint8_t)((1 << 0)));
assert(bits(0, 8) == (uint8_t)((1 << 0) | (1 << 8)));
assert(bits(8, 0) == (uint8_t)((1 << 8) | (1 << 0)));
assert(bits(1,2,3) == (uint8_t)((1 << 1) | (1 << 2) | (1 << 3)));
assert(bits(7,7,7) == (uint8_t)((1 << 7) | (1 << 7) | (1 << 7)));
assert(bits(6,2,3,4,0,7,5,1) ==
(uint8_t)((1 << 6) | (1 << 2) | (1 << 3) | (1 << 4) |
(1 << 0) | (1 << 7) | (1 << 5) | (1 << 1)));
assert(bits(6,2,3,4,0,7,5,1,2,2,2) ==
(uint8_t)((1 << 6) | (1 << 2) | (1 << 3) | (1 << 4) |
(1 << 0) | (1 << 7) | (1 << 5) | (1 << 1)));
}
我正在尝试编写一个宏来简化整数中多个位的设置。这通常发生在初始化配置寄存器时的微控制器代码中。例如,可以通过在寄存器 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.html and https://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))))
任何人都可以帮助我完成我的任务,或者告诉我这是不可能实现的吗?
将您的位宏定义为它们设置的实际位,不需要幻数硬编码,因为如果宏发生变化,它们的 _B 对应物也会发生变化:
#define WGM00_B (1u<<WGM00)
#define COM0A1_B (1u<<COM0A1)
#define WGM01_B (1u<<WGM01)
...
然后简单地或它们在一起,不需要宏,顺序无关紧要:
TCCR0A |= WGM00_B | COM0A1_B | WGM01_B;
或者放在宏里。用法就像你问的那样,但你使用按位 or operator.
而不是逗号TCCR0A |= BITS( WGM00_B | COM0A1_B | WGM01_B );
宏 BITS 简单定义为:
#defined BITS( b ) (b)
GCC 可变参数宏功能旨在在运行时将可变参数列表传递给类似于 printf 的函数,例如将 setBits( 3, WGM01, WGM00, COM0A1) 与 OR n 值一起。我怀疑在运行时对此进行评估是否可以接受。
如果你真的想像上面那样写 BITS,我想你可以使用宏处理器 m4,它允许递归定义并移动参数列表,所以你可以测试 $# 是 1 还是 >= 2。扩展会像这样运行:
1 TCCR0A |= BITS( WGM00 , COM0A1 , WGM01 );
2 TCCR0A |= (1u << WGM00) | BITS( COM0A1 , WGM01 ); # (1u << ) | 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<<``''),
(1u<<``'') | 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));
结果: 位 3( 0, 1, 2) 7 位 2( 0, 1) 3 BITS1( 0) 1
这是预期的。虽然这不是所希望的通用位设置宏,但解决方案很清晰,因此应该是可维护的。
Warning: Writing this was mostly a learning exercise.
DO NOT USE IN PRODUCTION CODE. People will rightly curse at you if you do.
因此,在进一步使用 Paul 的 answers and github 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;
}
几年后我找不到一个好的答案,所以我想到了这个...
- 一个
_bits
宏,它接受 8 个位位置参数(并接受但忽略任意数量的附加...
参数)。 - 一个
bits(...)
宏,将其参数连同 8 x 尾随8
一起传递给_bits
。 8
的结果为(uint8_t)(1 << 8) == 0
(或者如果它们属于_bits
的参数列表的...
部分则被忽略)。
注意 1. 被忽略的参数 x
用于支持空 args 的情况,bits() == 0
,这对于一致性来说似乎很好。例如你可能有空 #define OPTION_NO_FOO_DISABLED
并想要 bits(OPTION_NO_FOO) == 0
注2.只使用前8个参数。
例如bits(1,1,1,1,1,1,1,1,2) == 1
。 -- 谢谢弗里茨
#define _bits(x, i, j, k, l, m, n, o, p, ...) \
((uint8_t)( (uint8_t)(1U << (i)) | \
(uint8_t)(1U << (j)) | \
(uint8_t)(1U << (k)) | \
(uint8_t)(1U << (l)) | \
(uint8_t)(1U << (m)) | \
(uint8_t)(1U << (n)) | \
(uint8_t)(1U << (o)) | \
(uint8_t)(1U << (p)) ) )
#define bits(...) _bits(x, ## __VA_ARGS__, 8, 8, 8, 8, 8, 8, 8, 8)
int main()
{
assert(bits() == 0);
assert(bits(8) == 0);
assert(bits(7) == (uint8_t)((1 << 7)));
assert(bits(0) == (uint8_t)((1 << 0)));
assert(bits(0, 8) == (uint8_t)((1 << 0) | (1 << 8)));
assert(bits(8, 0) == (uint8_t)((1 << 8) | (1 << 0)));
assert(bits(1,2,3) == (uint8_t)((1 << 1) | (1 << 2) | (1 << 3)));
assert(bits(7,7,7) == (uint8_t)((1 << 7) | (1 << 7) | (1 << 7)));
assert(bits(6,2,3,4,0,7,5,1) ==
(uint8_t)((1 << 6) | (1 << 2) | (1 << 3) | (1 << 4) |
(1 << 0) | (1 << 7) | (1 << 5) | (1 << 1)));
assert(bits(6,2,3,4,0,7,5,1,2,2,2) ==
(uint8_t)((1 << 6) | (1 << 2) | (1 << 3) | (1 << 4) |
(1 << 0) | (1 << 7) | (1 << 5) | (1 << 1)));
}