为什么十六进制格式说明符在VS2013 for x86中产生错误的输出

时间:2015-11-26 11:59:01

标签: c

我在VS2013中遇到了一个与printf不同的问题,我在下面的小测试案例中已经将其隔离了。

使用x64编译器构建时一切都很好。当为32位构建时,其中一个变量打印不正确。无论是在Visual Studio内部构建还是在命令行使用cl,都会发生这种情况。它也会在禁用优化的情况下发生。

问题是printf中的十六进制格式说明符(或可能是UINT64)。当我从调用中删除格式说明符和变量时,它按预期工作。这与分割对printf的调用相同,它也可以正常工作。

显示不当行为的命令行输出如下。请注意content_layout的打印方式不同,首先为零(错误),然后为一(正确)。

我的问题 - 我在代码中做错了什么,或者编译器在x86上产生了错误的输出?

从cl输出并运行程序:

## Compile, disable optimisation with /Od
C:\Path\TestStruct>cl /Od main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.40629 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
Microsoft (R) Incremental Linker Version 12.00.40629.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj

C:\Path\TestStruct>main.exe

## This line is incorrect and prints zero for content_layout
handoff_size: 29336, handoff_location: 0xc8ff8000, content_layout: 0, content_type: 1

## These two lines are correct and print one for content_layout
## Note these are split up into two calls to printf()
handoff size: 29336, handoff_location: 0xc8ff8000
content_layout: 1, content_type: 1

测试用例代码如下:

#include <Windows.h>
#include <stdio.h>

// Don't allow compiler to align structs - pack members on byte boundary
#pragma pack(1)

typedef struct _acpi_header {
  char signature[4];
  UINT32 length;
  UINT8 revision;
  UINT8 checksum;
  char oem_id[6];
  char oem_table_id[8];
  UINT32 oem_revision;
  UINT32 creator_id;
  UINT32 creator_revision;
} acpi_header_t;

typedef struct _wpbt_header {
  UINT32 handoff_size;
  UINT64 handoff_location;
  UINT8 content_layout;
  UINT8 content_type;
} wpbt_header_t;

int main(void) {
  acpi_header_t *acpi_header;
  wpbt_header_t *wpbt_header;

  BYTE lpBuf[] = {
    0x57, 0x50, 0x42, 0x54, 0x38, 0x00, 0x00, 0x00, 0x01, 0xe4, 0x41, 0x42, 0x54, 0x2d, 0x4e, 0x54,
    0x41, 0x42, 0x54, 0x2d, 0x57, 0x50, 0x42, 0x54, 0x01, 0x00, 0x00, 0x00, 0x41, 0x42, 0x54, 0x57,
    0x02, 0x04, 0x12, 0x20, 0x98, 0x72, 0x00, 0x00, 0x00, 0x80, 0xff, 0xc8, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00
  };

  acpi_header = (acpi_header_t*)(lpBuf);
  wpbt_header = (wpbt_header_t*)(lpBuf + sizeof(acpi_header_t));

  // Does not work - content_layout is printed as 0 (zero)
  printf("handoff_size: %lu, handoff_location: 0x%x, content_layout: %u, content_type: %u\r\n", wpbt_header->handoff_size, 
    wpbt_header->handoff_location, wpbt_header->content_layout, wpbt_header->content_type);

  // Works - content_layout is printed as 1
  printf("handoff size: %lu, handoff_location: 0x%x\r\n", wpbt_header->handoff_size, wpbt_header->handoff_location);
  printf("content_layout: %u, content_type: %u", wpbt_header->content_layout, wpbt_header->content_type);

  getchar();

  return 0;
}

2 个答案:

答案 0 :(得分:2)

您使用错误的printf格式打印单个字节的问题。

如果您按照链接的参考并检查"%u"格式,您会看到没有任何前缀,它会打印unsigned int而不是UINT8(可能是unsigned char })。 unsigned char的正确格式为"%hhu"

但是,C已经标准化了一组predefined format macros,您应该使用它来代替固定宽度类型。要打印无符号的8位字节,应使用PRIu8

printf("content_layout: %" PRIu8 ", content_type: %" PRIu8 "\n",
       wpbt_header->content_layout, wpbt_header->content_type);

答案 1 :(得分:2)

handoff_location的格式说明符不正确。 %x用于int,在x86版本的代码中只有32位。但是你正在将64位int推入堆栈,这意味着,当printf处理它时,它会留下&#34; hand_off_location的前四个字节,并将它们解释为content_layout。反过来,content_layout实际上被解释为content_typecontent_type被忽略。

我要做的是使用%llx并将handoff_location投射到unsigned long long

另一点 - 在这种情况下你似乎已经逃脱了 - 是编译器可以自由地在结构的成员之间放置填充字节。你永远不应该只是将一个结构体叠加到一个二进制缓冲区上,并希望它有效。

64位版本可能有三个原因之一:

  • int是64位宽
  • 以64位对齐方式将项目推送到堆栈。
  • ABI for x64使用寄存器传递前几个参数。