[英]What are the effects of this define preprocessor directive?
我不明白这一点,但似乎这是一个函数:
#define GET_BE2(ptr) ((uint16_t)(ptr)[0] << 8 | (ptr)[1])
我需要理解这条线的意思,但我不能。 据我了解,您可以输入以下内容:
int *my_ptr = 1234;
int var;
var = GET_BE2(my_ptr);
这可能是超级错误的,但我只是想清楚地说明我不明白的地方。 我也说不清是什么
((uint16_t)(ptr)[0] << 8 | (ptr)[1])
是在做。 ptr
没有提到我们正在处理一个数组,那么我们为什么可以使用[]
? 然后,我可以告诉大家正在把8位左右的or-ing大概是什么是下一个8位。
所写的宏旨在用作函数。
#define GET_BE2(ptr) ((uint16_t)(ptr)[0] << 8 | (ptr)[1])
可能的意图是将两个连续的字节值转换为一个 16 位整数,假设字节以大端顺序呈现,因此ptr[0]
是更重要的字节,而ptr[1]
是不太重要的字节。
虽然没有显示文档,但您应该向它传递一个指向(至少)两个整数数组的指针,然后将对其进行位操作以产生结果。 因为它是一个宏,所以没有明确的类型约束。 因此,它可以通过以下任何一种方式调用:
signed char ptr0[2] = { 0x23, 0x37 };
signed short ptr1[2] = { 0x23, 0x37 };
signed int ptr2[2] = { 0x23, 0x37 };
signed long ptr3[2] = { 0x23, 0x37 };
signed long long ptr4[2] = { 0x23, 0x37 };
unsigned char ptr5[2] = { 0x23, 0x37 };
unsigned short ptr6[2] = { 0x23, 0x37 };
unsigned int ptr7[2] = { 0x23, 0x37 };
unsigned long ptr8[2] = { 0x23, 0x37 };
unsigned long long ptr9[2] = { 0x23, 0x37 };
给定显示的数据值,它甚至会从所有这些中产生相同的结果。
但是,如果任何有符号值是负数,或者如果将(转换后的)负值分配给任何其他unsigned char
(即ptr6
.. ptr9
)的任何无符号类型的第二个元素(如上面的0x37
所示),那么您不会得到预期的结果。
毫无疑问, ptr
应该是指向两个相邻unsigned char
值的指针。 然后,宏生成一个值,其中ptr[0]
中的值是uint16_t
值的高 8 位, ptr[1]
中的值是uint16_t
值的低 8 位。 结果是 0x2337。
如果类型大于char
或者 type 是signed char
(或普通char
类型是有符号的),并且如果ptr[1]
值为负,您会得到与预期不同的结果。
这是一个测试程序(其中有相当痛苦的重复——但摆脱重复也是痛苦的,对于所示的两个测试用例来说不值得):
#include <stdio.h>
#include <stdint.h>
#define GET_BE2(ptr) ((uint16_t)(ptr)[0] << 8 | (ptr)[1])
static void test1(void)
{
signed char ptr0[2] = { 0x23, 0x37 };
signed short ptr1[2] = { 0x23, 0x37 };
signed int ptr2[2] = { 0x23, 0x37 };
signed long ptr3[2] = { 0x23, 0x37 };
signed long long ptr4[2] = { 0x23, 0x37 };
unsigned char ptr5[2] = { 0x23, 0x37 };
unsigned short ptr6[2] = { 0x23, 0x37 };
unsigned int ptr7[2] = { 0x23, 0x37 };
unsigned long ptr8[2] = { 0x23, 0x37 };
unsigned long long ptr9[2] = { 0x23, 0x37 };
unsigned long long result;
printf("Two positive elements:\n");
result = GET_BE2(ptr0);
printf("ptr0[0] = 0x%.4hhX ptr0[1] = 0x%.16hhX ", ptr0[0], ptr0[1]);
printf("signed char = 0x%.16llX\n", result);
result = GET_BE2(ptr1);
printf("ptr1[0] = 0x%.4hX ptr1[1] = 0x%.16hX ", ptr1[0], ptr1[1]);
printf("signed short = 0x%.16llX\n", result);
result = GET_BE2(ptr2);
printf("ptr2[0] = 0x%.4X ptr2[1] = 0x%.16X ", ptr2[0], ptr2[1]);
printf("signed int = 0x%.16llX\n", result);
result = GET_BE2(ptr3);
printf("ptr3[0] = 0x%.4lX ptr3[1] = 0x%.16lX ", ptr3[0], ptr3[1]);
printf("signed long = 0x%.16llX\n", result);
result = GET_BE2(ptr4);
printf("ptr4[0] = 0x%.4llX ptr4[1] = 0x%.16llX ", ptr4[0], ptr4[1]);
printf("signed long long = 0x%.16llX\n", result);
result = GET_BE2(ptr5);
printf("ptr5[0] = 0x%.4hhX ptr5[1] = 0x%.16hhX ", ptr5[0], ptr5[1]);
printf("unsigned char = 0x%.16llX\n", result);
result = GET_BE2(ptr6);
printf("ptr6[0] = 0x%.4hX ptr6[1] = 0x%.16hX ", ptr6[0], ptr6[1]);
printf("unsigned short = 0x%.16llX\n", result);
result = GET_BE2(ptr7);
printf("ptr7[0] = 0x%.4X ptr7[1] = 0x%.16X ", ptr7[0], ptr7[1]);
printf("unsigned int = 0x%.16llX\n", result);
result = GET_BE2(ptr8);
printf("ptr8[0] = 0x%.4lX ptr8[1] = 0x%.16lX ", ptr8[0], ptr8[1]);
printf("unsigned long = 0x%.16llX\n", result);
result = GET_BE2(ptr9);
printf("ptr9[0] = 0x%.4llX ptr9[1] = 0x%.16llX ", ptr9[0], ptr9[1]);
printf("unsigned long long = 0x%.16llX\n", result);
}
static void test2(void)
{
signed char ptr0[2] = { 0x23, -0x00000037 };
signed short ptr1[2] = { 0x23, -0x00003A37 };
signed int ptr2[2] = { 0x23, -0x004B3A37 };
signed long ptr3[2] = { 0x23, -0x5C4B3A37 };
signed long long ptr4[2] = { 0x23, -0x5C4B3A37 };
unsigned char ptr5[2] = { 0x23, -0x00000037 };
unsigned short ptr6[2] = { 0x23, -0x00003A37 };
unsigned int ptr7[2] = { 0x23, -0x4B4B3A37 };
unsigned long ptr8[2] = { 0x23, -0x5C4B3A37 };
unsigned long long ptr9[2] = { 0x23, -0x5C4B3A37 };
unsigned long long result;
printf("One positive element, one negative element:\n");
result = GET_BE2(ptr0);
printf("ptr0[0] = 0x%.4hhX ptr0[1] = 0x%.16hhX ", ptr0[0], ptr0[1]);
printf("signed char = 0x%.16llX\n", result);
result = GET_BE2(ptr1);
printf("ptr1[0] = 0x%.4hX ptr1[1] = 0x%.16hX ", ptr1[0], ptr1[1]);
printf("signed short = 0x%.16llX\n", result);
result = GET_BE2(ptr2);
printf("ptr2[0] = 0x%.4X ptr2[1] = 0x%.16X ", ptr2[0], ptr2[1]);
printf("signed int = 0x%.16llX\n", result);
result = GET_BE2(ptr3);
printf("ptr3[0] = 0x%.4lX ptr3[1] = 0x%.16lX ", ptr3[0], ptr3[1]);
printf("signed long = 0x%.16llX\n", result);
result = GET_BE2(ptr4);
printf("ptr4[0] = 0x%.4llX ptr4[1] = 0x%.16llX ", ptr4[0], ptr4[1]);
printf("signed long long = 0x%.16llX\n", result);
result = GET_BE2(ptr5);
printf("ptr5[0] = 0x%.4hhX ptr5[1] = 0x%.16hhX ", ptr5[0], ptr5[1]);
printf("unsigned char = 0x%.16llX\n", result);
result = GET_BE2(ptr6);
printf("ptr6[0] = 0x%.4hX ptr6[1] = 0x%.16hX ", ptr6[0], ptr6[1]);
printf("unsigned short = 0x%.16llX\n", result);
result = GET_BE2(ptr7);
printf("ptr7[0] = 0x%.4X ptr7[1] = 0x%.16X ", ptr7[0], ptr7[1]);
printf("unsigned int = 0x%.16llX\n", result);
result = GET_BE2(ptr8);
printf("ptr8[0] = 0x%.4lX ptr8[1] = 0x%.16lX ", ptr8[0], ptr8[1]);
printf("unsigned long = 0x%.16llX\n", result);
result = GET_BE2(ptr9);
printf("ptr9[0] = 0x%.4llX ptr9[1] = 0x%.16llX ", ptr9[0], ptr9[1]);
printf("unsigned long long = 0x%.16llX\n", result);
}
int main(void)
{
test1();
test2();
return 0;
}
在运行 macOS Mojave 10.14.6 和 GCC 9.2.0 和 XCode 11.3.1 的 MacBook Pro 上,输出为:
Two positive elements:
ptr0[0] = 0x0023 ptr0[1] = 0x0000000000000037 signed char = 0x0000000000002337
ptr1[0] = 0x0023 ptr1[1] = 0x0000000000000037 signed short = 0x0000000000002337
ptr2[0] = 0x0023 ptr2[1] = 0x0000000000000037 signed int = 0x0000000000002337
ptr3[0] = 0x0023 ptr3[1] = 0x0000000000000037 signed long = 0x0000000000002337
ptr4[0] = 0x0023 ptr4[1] = 0x0000000000000037 signed long long = 0x0000000000002337
ptr5[0] = 0x0023 ptr5[1] = 0x0000000000000037 unsigned char = 0x0000000000002337
ptr6[0] = 0x0023 ptr6[1] = 0x0000000000000037 unsigned short = 0x0000000000002337
ptr7[0] = 0x0023 ptr7[1] = 0x0000000000000037 unsigned int = 0x0000000000002337
ptr8[0] = 0x0023 ptr8[1] = 0x0000000000000037 unsigned long = 0x0000000000002337
ptr9[0] = 0x0023 ptr9[1] = 0x0000000000000037 unsigned long long = 0x0000000000002337
One positive element, one negative element:
ptr0[0] = 0x0023 ptr0[1] = 0x00000000000000C9 signed char = 0xFFFFFFFFFFFFFFC9
ptr1[0] = 0x0023 ptr1[1] = 0x000000000000C5C9 signed short = 0xFFFFFFFFFFFFE7C9
ptr2[0] = 0x0023 ptr2[1] = 0x00000000FFB4C5C9 signed int = 0xFFFFFFFFFFB4E7C9
ptr3[0] = 0x0023 ptr3[1] = 0xFFFFFFFFA3B4C5C9 signed long = 0xFFFFFFFFA3B4E7C9
ptr4[0] = 0x0023 ptr4[1] = 0xFFFFFFFFA3B4C5C9 signed long long = 0xFFFFFFFFA3B4E7C9
ptr5[0] = 0x0023 ptr5[1] = 0x00000000000000C9 unsigned char = 0x00000000000023C9
ptr6[0] = 0x0023 ptr6[1] = 0x000000000000C5C9 unsigned short = 0x000000000000E7C9
ptr7[0] = 0x0023 ptr7[1] = 0x00000000B4B4C5C9 unsigned int = 0x00000000B4B4E7C9
ptr8[0] = 0x0023 ptr8[1] = 0xFFFFFFFFA3B4C5C9 unsigned long = 0xFFFFFFFFA3B4E7C9
ptr9[0] = 0x0023 ptr9[1] = 0xFFFFFFFFA3B4C5C9 unsigned long long = 0xFFFFFFFFA3B4E7C9
对于数组元素中的值足够小以适合范围 0..SCHAR_MAX (127) 的简单情况,输出符合预期,因为在提升值时,没有符号位使问题复杂化.
当值不是那么小时,表达式会产生意想不到的结果。
#define GET_BE2(ptr) ((uint16_t)(ptr)[0] << 8 | (ptr)[1])
让我们再添加几个括号:
#define GET_BE2(ptr) ((((uint16_t)(ptr)[0]) << 8) | (ptr)[1])
移位运算符被赋予 LHS 操作数的提升值。 这意味着, ptr[0]
首先被转换为一个uint16_t
值,那么这就是转换为int
(我假设一个“正常”的机器,其中sizeof(int) != sizeof(uint16_t)
结果左移8位。在的RHS |
运营商也晋升为int
;两个int
值相结合,并产生一个结果。请注意,转换, signed char
到int
信号扩展值(我假设2的补码表示;如果你”。担心 1 的补码或符号大小,请调整测试代码等以适应您的环境。)
这些因素导致在移位和/或运算符的操作数中设置各种无关位,从而导致“意外”结果。
为了使宏代码安全,需要更仔细地编写宏。 它可以使用0xFF
掩码或转换为uint8_t
(或unsigned char
)。
#define GET_BE2(ptr) (uint16_t)((((ptr)[0] & 0xFF) << 8) | ((ptr)[1] & 0xFF))
#define GET_BE2(ptr) ((((uint8_t)(ptr)[0]) << 8) | (uint8_t)(ptr)[1])
使用其中任何一个,输出是相同且自洽的:
Two positive elements:
ptr0[0] = 0x0023 ptr0[1] = 0x0000000000000037 signed char = 0x0000000000002337
ptr1[0] = 0x0023 ptr1[1] = 0x0000000000000037 signed short = 0x0000000000002337
ptr2[0] = 0x0023 ptr2[1] = 0x0000000000000037 signed int = 0x0000000000002337
ptr3[0] = 0x0023 ptr3[1] = 0x0000000000000037 signed long = 0x0000000000002337
ptr4[0] = 0x0023 ptr4[1] = 0x0000000000000037 signed long long = 0x0000000000002337
ptr5[0] = 0x0023 ptr5[1] = 0x0000000000000037 unsigned char = 0x0000000000002337
ptr6[0] = 0x0023 ptr6[1] = 0x0000000000000037 unsigned short = 0x0000000000002337
ptr7[0] = 0x0023 ptr7[1] = 0x0000000000000037 unsigned int = 0x0000000000002337
ptr8[0] = 0x0023 ptr8[1] = 0x0000000000000037 unsigned long = 0x0000000000002337
ptr9[0] = 0x0023 ptr9[1] = 0x0000000000000037 unsigned long long = 0x0000000000002337
One positive element, one negative element:
ptr0[0] = 0x0023 ptr0[1] = 0x00000000000000C9 signed char = 0x00000000000023C9
ptr1[0] = 0x0023 ptr1[1] = 0x000000000000C5C9 signed short = 0x00000000000023C9
ptr2[0] = 0x0023 ptr2[1] = 0x00000000FFB4C5C9 signed int = 0x00000000000023C9
ptr3[0] = 0x0023 ptr3[1] = 0xFFFFFFFFA3B4C5C9 signed long = 0x00000000000023C9
ptr4[0] = 0x0023 ptr4[1] = 0xFFFFFFFFA3B4C5C9 signed long long = 0x00000000000023C9
ptr5[0] = 0x0023 ptr5[1] = 0x00000000000000C9 unsigned char = 0x00000000000023C9
ptr6[0] = 0x0023 ptr6[1] = 0x000000000000C5C9 unsigned short = 0x00000000000023C9
ptr7[0] = 0x0023 ptr7[1] = 0x00000000B4B4C5C9 unsigned int = 0x00000000000023C9
ptr8[0] = 0x0023 ptr8[1] = 0xFFFFFFFFA3B4C5C9 unsigned long = 0x00000000000023C9
ptr9[0] = 0x0023 ptr9[1] = 0xFFFFFFFFA3B4C5C9 unsigned long long = 0x00000000000023C9
编写宏的人不太可能打算将它与char *
、 unsigned char *
或可能是signed char *
以外的任何东西一起使用(尽管很可能甚至没有考虑过signed char *
)。 因此,最好使用函数——最好是inline
函数——来完成这项工作。 这迫使您使用正确的类型(或转换不正确的类型):
static inline uint16_t get_be2(const unsigned char *ptr)
{
return (ptr[0] << 8) | ptr[1];
}
如果由于某种原因,您的编译器过于陈旧,它不会接受inline
(即使在整个千禧年它一直是标准 C 的一部分,但周围有这样的编译器),那么只需省略inline
。 编译器甚至可能根据自己的意愿使函数内联; 它可以看到它的使用位置,因为它仅限于当前文件,并且可以决定避免实际函数调用的开销是有意义的。 这是一个大大减少的测试用例——尽管这可以很容易地重新设计以消除大量重复。 请注意使用signed char
对调用的显式转换。
#include <stdio.h>
#include <stdint.h>
static inline uint16_t get_be2(const unsigned char *ptr)
{
return (ptr[0] << 8) | ptr[1];
}
#define GET_BE2(ptr) get_be2(ptr)
static void test1(void)
{
signed char ptr0[2] = { 0x23, 0x37 };
unsigned char ptr5[2] = { 0x23, 0x37 };
unsigned long long result;
printf("Two positive elements:\n");
result = GET_BE2((unsigned char *)ptr0);
printf("ptr0[0] = 0x%.4hhX ptr0[1] = 0x%.16hhX ", ptr0[0], ptr0[1]);
printf("signed char = 0x%.16llX\n", result);
result = GET_BE2(ptr5);
printf("ptr5[0] = 0x%.4hhX ptr5[1] = 0x%.16hhX ", ptr5[0], ptr5[1]);
printf("unsigned char = 0x%.16llX\n", result);
}
static void test2(void)
{
signed char ptr0[2] = { 0x23, -0x00000037 };
unsigned char ptr5[2] = { 0x23, -0x00000037 };
unsigned long long result;
printf("One positive element, one negative element:\n");
result = GET_BE2((unsigned char *)ptr0);
printf("ptr0[0] = 0x%.4hhX ptr0[1] = 0x%.16hhX ", ptr0[0], ptr0[1]);
printf("signed char = 0x%.16llX\n", result);
result = GET_BE2(ptr5);
printf("ptr5[0] = 0x%.4hhX ptr5[1] = 0x%.16hhX ", ptr5[0], ptr5[1]);
printf("unsigned char = 0x%.16llX\n", result);
}
int main(void)
{
test1();
test2();
return 0;
}
输出:
Two positive elements:
ptr0[0] = 0x0023 ptr0[1] = 0x0000000000000037 signed char = 0x0000000000002337
ptr5[0] = 0x0023 ptr5[1] = 0x0000000000000037 unsigned char = 0x0000000000002337
One positive element, one negative element:
ptr0[0] = 0x0023 ptr0[1] = 0x00000000000000C9 signed char = 0x00000000000023C9
ptr5[0] = 0x0023 ptr5[1] = 0x00000000000000C9 unsigned char = 0x00000000000023C9
char
与unsigned char
和有signed char
共有三种不同的(单字节)字符类型:(纯) char
、 signed char
和unsigned char
。 普通char
类型可以是有符号或无符号的; 这是一个必须记录在案的实施决定。 我没有费心在解释中显示char
,因为它的行为与signed char
(这是它在 Mac 上的行为方式)或unsigned char
。 然而,在实践中,代码通常是使用普通的char
编写的。 如果修改函数以采用普通char
指针,则必须确保它正常工作,无论普通char
类型是有符号还是无符号。 在这种情况下,您可以将传入的const char *ptr
为const unsigned char *uptr = (unsigned char *)ptr;
并参考uptr[0]
和uptr[1]
,或者像在固定宏变体中一样添加演员表或掩码。
使用inline
函数。 它强制执行类型正确性。 它完全避免了宏的问题。 而且,因为这个函数足够小,编译器几乎肯定能够内联代码,所以与宏版本相比,它是免费的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.