当位被屏蔽时使用不正确的格式说明符是否是未定义的行为

Is it undefined behaviour to use the incorrect format specifiers when bits were masked away

如果我有一些虚构的 uint32_t 并且我有兴趣查看每个字节,使用 uint8_t 而不是 uint32_t 的格式说明符是否是未定义的行为。请参阅下面的示例以了解我的意思。

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    uint32_t myInt = 0xFF8BA712;
    
    printf("1: %" PRIu8 ", 2: %" PRIu8 ", 3: %" PRIu8 ", 4: %" PRIu8 "\n", myInt & 0xFF, (myInt >> 8) & 0xFF, (myInt >> 16) & 0xFF, (myInt >> 24) & 0xFF);
    
    return 0;
}

使用编译命令:gcc test.c -Wall -Wextra -pedantic -std=c2x 我没有收到任何编译警告或错误。这在我看来应该没问题。但是,我确实在处理位深度为 32bpp 的图像的代码库中使用了这样的代码,并且我经常需要从整个像素中提取单个字节才能使用它们。因此,我想避免这种关于打印这些字节(如果存在)的未定义行为。

我强烈建议首先将所有值转换为 uint8_t,既为了清晰又为了一致性(即:PRIu8 需要 uint8_t 类型的值):

// something like this
uint32_t x = 5;
printf("%" PRIu8 "\n", (uint8_t) (x >> 24));

但如果你不投射,它应该会继续工作。为什么?因为当您在可变参数函数中使用类型 uint8_t 的值(可能 a.k.a。对于 unsigned char)时,它们会被提升为 int。实际上,在我的平台上,"%" PRIu8%u 的别名(即:unsigned int 的格式说明符)。但是,int 的大小是实现定义的,因此如果您没有将值正确地转换为正确的说明符(即:将 uint8_t 用于 PRIu8,则可能会出现严重错误等)。

如果您不强制转换可能无法运行的代码示例:

uint64_t x = 5;
printf("%" PRIu8 "\n", x >> 56);

clang-cl 编译器(在 Visual Studio 2019 年)为您的代码提供以下四个警告:

warning : format specifies type 'unsigned char' but the argument has type 'unsigned int' [-Wformat]

现在,虽然严格来说,传递一个不适合其相应格式说明符的类型的参数未定义的行为,在这种情况下,无论如何,printf 函数的参数将被提升为它们各自的 int 等价物。来自 this C11 Draft Standard:

7.21.6.1 The fprintf function


7     The length modifiers and their meanings are:
       hh     Specifies that a following d, i, o, u, x, or X conversion specifier applies
                 to a signed char or unsigned char argument (the argument will have been
                 promoted according to the integer promotions, but its value shall be converted
                 to signed char or unsigned char before printing); …

您可以通过将每个参数转换为 uint8_t 来删除警告,但上面的摘录表明(至少对我而言)这不会产生真正的区别:

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    uint32_t myInt = 0xFF8BA712;
    printf("1: %" PRIu8 ", 2: %" PRIu8 ", 3: %" PRIu8 ", 4: %" PRIu8 "\n",
        (uint8_t)(myInt & 0xFF), (uint8_t)((myInt >> 8) & 0xFF), (uint8_t)((myInt >> 16) & 0xFF),
        (uint8_t)((myInt >> 24) & 0xFF));

    return 0;
}

请注意,在我的系统上,PRIu8 被定义为 "hhu",而 uint8_t 等同于 unsigned char – 许多情况下可能都是这种情况其他平台。