输出中 C 结构组件中的前导 0xff

Leading 0xff in C structure components in the output

最奇怪的事情正在发生。遵循代码:

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

struct s
{
  char a;
  char b;
  char c;

  unsigned int d;
  unsigned int e;
}__attribute__((packed));

int main(void)
{
  struct s *m = (void *)malloc(sizeof (struct s));
  assert(m);

  m->a=0x33;
  m->b=0x33;
  m->c=0x33;
  m->d=0xabcdefab;
  m->e=0x12345678;
  *(char *)(m + 1) = 0;

  while (*((char*)m))
  {
    printf("%p -> %x\n", m, *((char *)m));
    m = (struct s *)((char*) m + 1);
  }
}

这是输出:

0x85a010 -> 33
0x85a011 -> 33
0x85a012 -> 33
0x85a013 -> ffffffab
0x85a014 -> ffffffef
0x85a015 -> ffffffcd
0x85a016 -> ffffffab
0x85a017 -> 78
0x85a018 -> 56
0x85a019 -> 34
0x85a01a -> 12

到目前为止,您可能已经发现了输出中的奇怪之处。确实,内存中地址0x85a013到0x85a016处没有'0xff',也不是每个字节4个字节?!?! (我的意思是,这合乎逻辑吗?)。在我看来,这是一个显示问题,但我无法弄清楚为什么或如何。

编辑

(char *) 转换为 (unsigned char *)。并且为了防止未定义的行为,不要尝试 post 哨兵,而是使用结构大小。我的版本:

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

#pragma pack(push, 1)
struct s
{
    char a;
    char b;
    char c;
    unsigned int d;
    unsigned int e;
};
#pragma pack(pop)

int main(void)
{
    int i;
    unsigned char *cptr;
    struct s *m = malloc(sizeof (struct s));
    assert(m);

    m->a=0x33;
    m->b=0x33;
    m->c=0x33;
    m->d=0xabcdefab;
    m->e=0x12345678;

    cptr = (unsigned char*)m;
    for (i=0; i<sizeof(struct s); i++)
        printf("%p -> %02x\n", (void*)(cptr+i), cptr[i]);
}

程序输出:

001C2DD0 -> 33
001C2DD1 -> 33
001C2DD2 -> 33
001C2DD3 -> ab
001C2DD4 -> ef
001C2DD5 -> cd
001C2DD6 -> ab
001C2DD7 -> 78
001C2DD8 -> 56
001C2DD9 -> 34
001C2DDA -> 12

这是由有符号整数提升引起的。 %x 的 printf 说明符至少需要一个完整大小的 int 值。

为了避免值大小的各种问题,printf 等可变参数函数将值至少转换为 intdouble 大小。

这意味着像 0xab 这样的 signed char 值被符号扩展为 32 位 0xffffffab

格式说明符 %x 需要 printf 中的 unsigned int 个参数。仅当 int 值为非负值时才允许在其位置使用 int 参数。如果值为负,则行为未定义。

您正在为 %x 格式说明符传递 char 参数。可变参数 char 会自动转换为 int 并作为 int 传递。如果原始 char(以及结果 int)恰好具有负值,则行为未定义。这显然是你的情况。您的 ff 输出是您尝试使用 %x 格式说明符打印负 int 值的无效尝试的结果。

您可以通过多种不同的方式修复损坏的代码。例如,您可以将 printf 参数转换为 unsigned int 显式类型

printf("%p -> %x\n", (void *) m, (unsigned) *((char *)m));

这将消除未定义的行为,但很可能会使输出保持不变。现在 ff 将显示为负值模转换为 unsigned int 类型的 [完全定义] 结果。

这个

m = (struct s *)((char*) m + 1);

也是一种形式上有问题的做法。该语言不能保证 struct 指针可以保留 char * 指针的准确值。