2016-01-31 7 views
3

Я пытаюсь написать макрос, который упрощает установку нескольких битов в целое число. Это обычно происходит в коде микроконтроллера при инициализации регистров конфигурации. Например, можно настроить 8-разрядный таймер, установив 3 бита в регистре TCCR0A так:Написание переменного макроса, который устанавливает определенные биты в целое число (бит-маска)

// 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 и 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)))) 

Может кто-нибудь помочь мне с моими поисками, или скажите мне, что это невозможно достичь этого?

+2

Вы не должны использовать имена, начинающиеся с подчеркивания, поскольку они [зарезервированы] (http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html). – e0k

+0

@ e0k Вы настроены правильно. Я назвал его '_BITS' совместимым с макросом' _BV' из avr-libc и потому, что он будет в моей собственной «частной библиотеке». – Fritz

+1

'(1 << bit) ' -->>' (1u << бит) '. Это помогает. Иногда. – wildplasser

ответ

3

Предупреждение: Написание это было в основном изучение физических упражнений.

НЕ ИСПОЛЬЗУЙТЕ В ПРОДУКЦИОННОМ КОДЕ. Люди будут справедливо проклинать вас, если вы это сделаете.

Таким образом, после игры вокруг немного больше с макросами от Павла answers и 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; 
} 
+0

Вау, это безумие. – 2501

+0

@ 2501 Э-э ... спасибо? : D – Fritz

+0

@ 2501 Не поймите меня неправильно. Я полностью согласен с тем, что ваше решение является лучшим в принципе *. Однако на практике эти константы битмаски не определены в avr-libc, и их определение вручную не реально. Таким образом, этот хакер. – Fritz

1

Определить вы укусили макросы как фактические биты, которые они ставят, не магическое число не жестко прописывать требуются, Becase если макросы изменяются таким образом сделать их _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) 
+0

На самом деле это не отвечает на вопрос, потому что я не просил о лучших практиках, а помогал в написании конкретного макроса. Кроме того, он поражает точку констант, как 'WGM01' (что означает« Режим генерации формы волны TImer 0, бит 1 »). Когда я пишу '1 << WGM01', мне не нужно заботиться о значении' WGM01', которое определено в заголовке, специфичном для архитектуры. С вашим решением я по сути жестко его кодировал (так же, как писать «1 << 6», что плохо, потому что 6 - «магическое число»). – Fritz

+0

@Fritz Нет, вы бы написали WGM01_B, который будет определен как (1 << WGM01). Таким образом, вам не нужно заботиться о значении 'WGM01'. – 2501

+0

Спасибо. Однако основная проблема с вашим подходом состоит в том, что существует несколько сотен таких предопределенных констант. Вручную перебирать все файлы заголовков и определять несколько сотен дополнительных констант как-то поражает цель. В этом случае было бы проще просто написать '1 << WGM01' и т. Д. Вот почему я ищу написать один макрос, который делает это для меня. – Fritz

2

ССАГПЗ VARIADIC макросъёмки, намерен передать список переменных аргументов во время выполнения, к функции, аналогичной Printf, скажем setBits (3, WGM01 , WGM00, COM0A1) до OR n вместе. Я сомневаюсь, что оценка этого во время выполнения приемлема.

Если вы действительно хотите написать BITS, как указано выше, я думаю, вы могли бы использовать макропроцессор m4, который позволяет рекурсивное определение с перемещением списка аргументов, так что вы можете проверить $ # is 1 или> = 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([email protected]))')') 

В уловок 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

Что ожидаются. Хотя это не общий набор макросов, на который рассчитывается макрос, решение имеет ясность, поэтому его следует поддерживать.

+0

Насколько я понимаю, ваше первое предложение не совсем корректно. Параметрические макрокоманды '...' просто вставляются (разделяются запятыми) в расширенный макрос, где вы пишете '__VA_ARGS__'. Тот факт, что вы * можете * передать эти вариационные функции, такие как 'printf', не означает, что вы * должны *. Взгляните на вторую ссылку в вопросе, там вы найдете несколько примеров вариативных макросов, ни один из которых не оценивается во время выполнения. Весь смысл этой статьи состоит в том, чтобы оценивать все во время предварительной обработки. – Fritz

+0

Точка взята. Но эти «трюки» CPP не показывают, как писать BITS (m1, m2, .., mn), в CPP нет совпадения шаблона стиля Haskel, чтобы выбрать правило для дальнейшего расширения путем сопоставления в соответствии с параметром. – Rob11311

+0

Хорошо, можно создать условие завершения для макрорекурсии. См. Мой ответ. – Fritz