繁体   English   中英

这个定义预处理器指令的效果是什么?

[英]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 charint信号扩展值(假设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

普通charunsigned char和有signed char

共有三种不同的(单字节)字符类型:(纯) charsigned charunsigned char 普通char类型可以是有符号或无符号的; 这是一个必须记录在案的实施决定。 我没有费心在解释中显示char ,因为它的行为与signed char (这是它在 Mac 上的行为方式)或unsigned char 然而,在实践中,代码通常是使用普通的char编写的。 如果修改函数以采用普通char指针,则必须确保它正常工作,无论普通char类型是有符号还是无符号。 在这种情况下,您可以将传入的const char *ptrconst unsigned char *uptr = (unsigned char *)ptr; 并参考uptr[0]uptr[1] ,或者像在固定宏变体中一样添加演员表或掩码。

首选方案

使用inline函数。 它强制执行类型正确性。 它完全避免了宏的问题。 而且,因为这个函数足够小,编译器几乎肯定能够内联代码,所以与宏版本相比,它是免费的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM