Что макросы делать
Игнорирование проблемы, описанные в следующем разделе, макросы рассматривать массив интегрального типа как массив 8-битных значений, и когда попросили работать на долото k
, обрабатывает k%8
th бит k/8
th элемент массива.
Однако, вместо использования k % 8
или k/8
, он использует сдвиги и маскировку.
# define SETBIT(A, k) { A[k >> 3] |= (01 << (k & 07)); }
# define CLRBIT(A, k) { A[k >> 3] &= ~(01 << (k & 07)); }
# define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
k >> 3
сдвигает значения вправо на 3 битовых позиций, эффективно деления на 8.
k & 07
извлекает 3 наименее значимых битов (так как 07
восьмеричное или 7
десятичное в двоичное 111
), не обращая внимания на остальные ,
сдвигает значение 1 влево на 0..7 бит в зависимости от значения k & 07
, производя одно из двоичных значений:
0000 0001
0000 0010
0000 0100
0000 1000
0001 0000
0010 0000
0100 0000
1000 0000
Формально это на самом деле int
значение, и, следовательно, возможно, имеет 32 бита, но биты верхнего порядка - все нули.
Оператор ~
преобразует каждый бит 0 в 1 и каждый 1 бит в 0.
- The
&
оператора сочетает в себе два значения, получая 1 бит, где оба бита равны 1 и 0, где один или оба биты равны 0.
- оператор
|
сочетает в себе два значения, получая 0 бит, где оба бита равны 0 и 1, где один или оба бита равны 1.
- операторы присваивания
|=
и &=
применить операнд на РИТ, чтобы переменная на LHS. Обозначение a |= b;
эквивалентно a = a | b;
, за исключением того, что a
оценивается только один раз. Эта деталь здесь не имеет значения; это имеет значение интенсивно, если в выражении a
есть приращение или что-то подобное.
Собираем все вместе:
SETBIT
устанавливает k
й бит (то есть устанавливает его в 1) в массиве 8-битовых значений, представленных A
.
CLRBIT
сбрасывает k
th бит (значение устанавливает его на 0) в массиве 8-битных значений, представленных A
.
GETBIT
находит значение в бите k
й в массиве 8-битовых значений, представленных A
, и возвращает его в качестве либо 0
или 1
- это то, что конечный >> (k & 07)
делает.
Номинально, элементы массива должны быть unsigned char
, чтобы избежать проблем со значениями и неиспользуемого пространства, но любой интегральный тип может быть использован, более или менее расточительно. Вы получите интересные результаты, если тип signed char
, а высокие биты установлены на значениях, или если тип равен char
, а обычный char
- это подписанный тип. Вы также можете получить интересные результаты от GETBIT
, если тип A
является целым типом, большим, чем char
, а значения в массиве имеют биты, установленные за последние (наименее значимые) 8 бит числа.
Что макросы не делают
макросов, предоставленных профессором является предметным уроком в том, как не писать C препроцессор макросы. Они не учат вас писать хороший C; они учат, как писать ужасно ужасно C.
Каждый из этих макросов опасно сломан, потому что аргумент k
не заключен в круглые скобки при его использовании. Нетрудно утверждать, что то же самое относится и к A
. Использование 01
и 07
не совсем ошибочно, но октал 01
и 07
такие же, как десятичные 1
и 7
.
Макрос GETBIT
также нуждается в дополнительном уровне круглых скобок вокруг всего его тела. Учитывая
int y = 2;
unsigned char array[32] = "abcdefghijklmnopqrstuvwxyz";
, то это не компилируется:
int x = GETBIT(array + 3, y + 2) + 13;
Это компилировать (с предупреждениями), если ваши опции компилятора достаточно слабые, но будет производить эксцентричный результат:
int x = GETBIT(3 + array, y + 2) + 13;
и это до того, как мы попытаемся обсудить:
int x = GETBIT(3 + array, y++) + 13;
В CLRBIT и SetBit макросов использовать фигурные скобки, что означает, что вы не можете написать:
if (GETBIT(array, 13))
SETBIT(array, 27);
else
CLRBIT(array, 19);
потому, что точка с запятой после SETBIT
является пустым оператором после закрывающей фигурной скобки в блоке заявления, введенном SETBIT
, поэтому п else
просто синтаксически неверно.
макросов может быть написан как это (с сохранением структуры блока операторов для SETBIT
и CLRBIT
макросов):
#define SETBIT(A, k) do { (A)[(k) >> 3] |= (1 << ((k) & 7)); } while (0)
#define CLRBIT(A, k) do { (A)[(k) >> 3] &= ~(1 << ((k) & 7)); } while (0)
#define GETBIT(A, k) (((A)[(k) >> 3] & (1 << ((k) & 7))) >> ((k) & 7))
do { … } while (0)
обозначения является стандартным методом в макросах, позволяет обойти проблему взлома if
/else
заявлений.
Макросы также можно переписать так, потому что задания являются выражениями:
#define SETBIT(A, k) ((A)[(k) >> 3] |= (1 << ((k) & 7)))
#define CLRBIT(A, k) ((A)[(k) >> 3] &= ~(1 << ((k) & 7)))
#define GETBIT(A, k) (((A)[(k) >> 3] & (1 << ((k) & 7))) >> ((k) & 7))
Или, еще лучше, так как static inline
функции, как это:
static inline void SETBIT(unsigned char *A, int k) { A[k >> 3] |= (1 << (k & 7)); }
static inline void CLRBIT(unsigned char *A, int k) { A[k >> 3] &= ~(1 << (k & 7)); }
static inline int GETBIT(unsigned char *A, int k) { return (A[k >> 3] & (1 << (k & 7))) >> (k & 7); }
Все могут быть собраны в простой программа испытаний:
#if MODE == 1
/* As provided */
#define SETBIT(A, k) { A[k >> 3] |= (01 << (k & 07)); }
#define CLRBIT(A, k) { A[k >> 3] &= ~(01 << (k & 07)); }
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
#elif MODE == 2
/* As rewritten */
#define SETBIT(A, k) do { (A)[(k) >> 3] |= (1 << ((k) & 7)); } while (0)
#define CLRBIT(A, k) do { (A)[(k) >> 3] &= ~(1 << ((k) & 7)); } while (0)
#define GETBIT(A, k) (((A)[(k) >> 3] & (1 << ((k) & 7))) >> ((k) & 7))
#else
/* As rewritten */
static inline void SETBIT(unsigned char *A, int k) { A[k >> 3] |= (1 << (k & 7)); }
static inline void CLRBIT(unsigned char *A, int k) { A[k >> 3] &= ~(1 << (k & 7)); }
static inline int GETBIT(unsigned char *A, int k) { return (A[k >> 3] & (1 << (k & 7))) >> (k & 7); }
#endif
int main(void)
{
int y = 2;
unsigned char array[32] = "abcdefghijklmnopqrstuvwxyz";
int x = GETBIT(array + 3, y + 2) + 13;
int z = GETBIT(3 + array, y + 2) + 13;
if (GETBIT(array, 3))
SETBIT(array, 22);
else
CLRBIT(array, 27);
return x + z;
}
При составлении с -DMODE=2
или -DMODE=0
или без каких-либо настроек -DMODE
, тогда он чист. Когда скомпилировано -DMODE=1
, есть недопустимое количество предупреждений (ошибки для меня, потому что я использую GCC и компилирую с -Werror
, что делает любое предупреждение в ошибке).
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=0 bits23.c -o bits23
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=2 bits23.c -o bits23
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DMODE=1 bits23.c -o bits23
bits23.c: In function ‘main’:
bits23.c:28:33: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
int x = GETBIT(array + 3, y + 2) + 13;
^
bits23.c:6:25: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:6:24: error: subscripted value is neither array nor pointer nor vector
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:28:13: note: in expansion of macro ‘GETBIT’
int x = GETBIT(array + 3, y + 2) + 13;
^
bits23.c:28:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
int x = GETBIT(array + 3, y + 2) + 13;
^
bits23.c:6:43: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:28:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
int x = GETBIT(array + 3, y + 2) + 13;
^
bits23.c:6:57: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:29:33: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
int z = GETBIT(3 + array, y + 2) + 13;
^
bits23.c:6:25: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:29:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
int z = GETBIT(3 + array, y + 2) + 13;
^
bits23.c:6:43: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:29:22: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
int z = GETBIT(3 + array, y + 2) + 13;
^
bits23.c:6:23: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:29:33: error: suggest parentheses around ‘+’ in operand of ‘&’ [-Werror=parentheses]
int z = GETBIT(3 + array, y + 2) + 13;
^
bits23.c:6:57: note: in definition of macro ‘GETBIT’
#define GETBIT(A, k) (A[k >> 3] & (01 << (k & 07))) >> (k & 07)
^
bits23.c:29:38: error: suggest parentheses around ‘+’ inside ‘>>’ [-Werror=parentheses]
int z = GETBIT(3 + array, y + 2) + 13;
^
bits23.c:33:5: error: ‘else’ without a previous ‘if’
else
^
cc1: all warnings being treated as errors
$
Если вы знаете, что делают операции '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ' x = x op y', вы можете понять это сами, работая над некоторыми примерами. Попробуйте k == 7 и k == 8 ... Подсказки: A - это массив из 8-битных байтов, правый сдвиг на три - это то же самое, что и деление на 8, а двоичное представление 7 равно 00000111. – keshlam