出于调试目的,我如何强制设置 int 的大小?

How can I force the size of an int for debugging purposes?

我正在开发的软件有两个版本,一个用于 int 大小为 16 位的嵌入式系统,另一个用于在 int 大小为 32 位的桌面上进行测试.我正在使用来自 <stdint.h> 的固定宽度整数类型,但整数提升规则仍然取决于 int 的大小。

理想情况下,我希望像下面的代码那样打印 65281(整数提升到 16 位)而不是 4294967041(整数提升到 32 位),因为整数提升,所以它完全匹配嵌入式系统上的行为。我想确保在我的桌面上测试期间给出一个答案的代码在嵌入式系统上给出完全相同的答案。 GCC 或 Clang 的解决方案都可以。

#include <stdio.h>
#include <stdint.h>

int main(void){
    uint8_t a = 0;
    uint8_t b = -1;

    printf("%u\n", a - b);

    return 0;
}

编辑:

我给出的示例可能不是最好的示例,但我确实希望整数提升为 16 位而不是 32 位。举个例子:

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

int main(void){
    uint16_t a = 0;
    uint16_t b = 1;
    uint16_t c = a - 2; // "-2": 65534
    uint16_t d = (a - b) / (a - c);

    printf("%" PRIu16 "\n", d);

    return 0;
}

在 32 位系统上输出是 0,因为 t运行 提升为(有符号)int 后整数除法的 cation,而不是 32767

到目前为止最好的答案似乎是使用模拟器,这不是我所希望的,但我想确实有道理。从理论上讲,编译器似乎确实有可能生成代码,其行为就好像 int 的大小是 16 位一样,但我想在实践中没有简单的方法可以做到这一点,这也许不足为奇,对这种模式和任何必要的 运行时间支持的需求可能不大。

编辑 2:

这就是我到目前为止所探索的内容:实际上有一个 GCC 版本以 16 位模式的 i386 为目标 https://github.com/tkchia/gcc-ia16。输出的是一个DOS的COM文件,在DOSBox中可以是运行。比如两个文件:

test.c

#include <stdint.h>

uint16_t result;

void test16(void){
    uint16_t a = 0;
    uint16_t b = 1;
    uint16_t c = a - 2; // "-2": 65534
    result = (a - b) / (a - c);
}

main.c

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

extern uint16_t result;
void test16(void);

int main(void){
    test16();
    printf("result: %" PRIu16"\n", result);

    return 0;
}

可以用

编译
$ ia16-elf-gcc -Wall test16.c main.c -o a.com

生成a.com,可以在DOSBox中运行。

D:\>a
result: 32767

进一步研究一下,ia16-elf-gcc 实际上会生成一个 32 位 elf 作为中间体,尽管默认情况下最终 link 输出是一个 COM 文件:

$ ia16-elf-gcc -Wall -c test16.c -o test16.o
$ file test16.o
test16.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

我可以用 main.c 强制它 link 使用常规 GCC 编译,但毫不奇怪,生成的可执行文件出现段错误。

$ gcc -m32 -c main.c -o main.o
$ gcc -m32 -Wl,-m,elf_i386,-s,-o,outfile test16.o main.o
$ ./outfile
Segmentation fault (core dumped)

从 post here 看来,理论上应该可以 link ia16-elf-gcc 的 16 位代码输出到 32 位代码,虽然我实际上不确定如何。然后还有在 64 位 OS 上实际 运行ning 16 位代码的问题。更理想的编译器仍然使用常规的 32-bit/64-bit 寄存器和指令来执行算术,但通过库调用模拟算术,类似于例如 uint64_t 在(非 - 64 位)微控制器。

我能找到的最接近 x86-64 上实际 运行ning 16 位代码的是 here,它似乎 experimental/completely 未维护。在这一点上,只使用模拟器开始似乎是最好的解决方案,但我会再等一会儿,看看是否有其他人有任何想法。

编辑 3

我将继续接受 antti 的回答,尽管这不是我希望听到的答案。如果有人对 ia16-elf-gcc 的输出感兴趣(我以前从未听说过 ia16-elf-gcc),这里是反汇编:

$ objdump -M intel -mi386 -Maddr16,data16 -S test16.o > test16.s

请注意,您必须指定它是 16 位代码,否则 objdump 会将其解释为 32 位代码,映射到不同的指令(见下文)。

test16.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:  55                      push   bp     ; save frame pointer
1:  89 e5                   mov    bp,sp  ; copy SP to frame pointer
3:  83 ec 08                sub    sp,0x8 ; allocate 4 * 2bytes on stack
6:  c7 46 fe 00 00          mov    WORD PTR [bp-0x2],0x0 ; uint16_t a = 0
b:  c7 46 fc 01 00          mov    WORD PTR [bp-0x4],0x1 ; uint16_t b = 1
10: 8b 46 fe                mov    ax,WORD PTR [bp-0x2]  ; ax = a
13: 83 c0 fe                add    ax,0xfffe             ; ax -= 2
16: 89 46 fa                mov    WORD PTR [bp-0x6],ax  ; uint16_t c = ax = a - 2
19: 8b 56 fe                mov    dx,WORD PTR [bp-0x2]  ; dx = a
1c: 8b 46 fc                mov    ax,WORD PTR [bp-0x4]  ; ax = b
1f: 29 c2                   sub    dx,ax                 ; dx -= b
21: 89 56 f8                mov    WORD PTR [bp-0x8],dx  ; temp = dx = a - b
24: 8b 56 fe                mov    dx,WORD PTR [bp-0x2]  ; dx = a
27: 8b 46 fa                mov    ax,WORD PTR [bp-0x6]  ; ax = c
2a: 29 c2                   sub    dx,ax                 ; dx -= c (= a - c)
2c: 89 d1                   mov    cx,dx                 ; cx = dx = a - c
2e: 8b 46 f8                mov    ax,WORD PTR [bp-0x8]  ; ax = temp = a - b
31: 31 d2                   xor    dx,dx                 ; clear dx
33: f7 f1                   div    cx                    ; dx:ax /= cx (unsigned divide)
35: 89 c0                   mov    ax,ax                 ; (?) ax = ax
37: 89 c0                   mov    ax,ax                 ; (?) ax = ax
39: a3 00 00                mov    ds:0x0,ax             ; ds[0] = ax
3c: 90                      nop
3d: 89 c0                   mov    ax,ax                 ; (?) ax = ax
3f: 89 ec                   mov    sp,bp                 ; restore saved SP
41: 5d                      pop    bp                    ; pop saved frame pointer
42: 16                      push   ss  ;      ss
43: 1f                      pop    ds  ; ds =
44: c3                      ret

在GDB中调试程序,这条指令导致段错误

movl   [=20=]x46c70000,-0x2(%esi)

这是用32位模式解码的指令解释的前两条设置a和b值的移动指令。相关反汇编(不指定16位模式)如下:

$ objdump -M intel  -S test16.o > test16.s && cat test16.s

test16.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:   55                      push   ebp
1:   89 e5                   mov    ebp,esp
3:   83 ec 08                sub    esp,0x8
6:   c7 46 fe 00 00 c7 46    mov    DWORD PTR [esi-0x2],0x46c70000
d:   fc                      cld    

下一步是想办法让处理器进入 16 位模式。它甚至不必是实模式(google 搜索大多会出现 x86 16 位实模式的结果),它甚至可以是 16 位保护模式。但在这一点上,使用模拟器绝对是最好的选择,这更多是出于我的好奇心。这也是特定于 x86 的。作为参考,这里是在 32 位模式下编译的同一个文件,它隐式提升为 32 位有符号整数(来自 运行ning gcc -m32 -c test16.c -o test16_32.o && objdump -M intel -S test16_32.o > test16_32.s):

test16_32.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:  55                      push   ebp      ; save frame pointer
1:  89 e5                   mov    ebp,esp  ; copy SP to frame pointer
3:  83 ec 10                sub    esp,0x10 ; allocate 4 * 4bytes on stack
6:  66 c7 45 fa 00 00       mov    WORD PTR [ebp-0x6],0x0 ; uint16_t a = 0
c:  66 c7 45 fc 01 00       mov    WORD PTR [ebp-0x4],0x1 ; uint16_t b = 0
12: 0f b7 45 fa             movzx  eax,WORD PTR [ebp-0x6] ; eax = a
16: 83 e8 02                sub    eax,0x2                ; eax -= 2
19: 66 89 45 fe             mov    WORD PTR [ebp-0x2],ax  ; uint16_t c = (uint16_t) (a-2)
1d: 0f b7 55 fa             movzx  edx,WORD PTR [ebp-0x6] ; edx = a
21: 0f b7 45 fc             movzx  eax,WORD PTR [ebp-0x4] ; eax = b
25: 29 c2                   sub    edx,eax                ; edx -= b
27: 89 d0                   mov    eax,edx                ; eax = edx (= a - b)
29: 0f b7 4d fa             movzx  ecx,WORD PTR [ebp-0x6] ; ecx = a
2d: 0f b7 55 fe             movzx  edx,WORD PTR [ebp-0x2] ; edx = c
31: 29 d1                   sub    ecx,edx                ; ecx -= edx (= a - c)
33: 99                      cdq                           ; EDX:EAX = EAX sign extended (= a - b)
34: f7 f9                   idiv   ecx                    ; EDX:EAX /= ecx
36: 66 a3 00 00 00 00       mov    ds:0x0,ax              ; ds = (uint16_t) ax
3c: 90                      nop
3d: c9                      leave                         ; esp = ebp (restore stack pointer), pop ebp
3e: c3                      ret

你不能,除非你找到一些非常特殊的编译器。它会破坏 绝对一切 ,包括您的 printf 调用。 32 位编译器中的代码生成甚至可能 无法 生成 16 位算术代码,因为通常不需要它。

您是否考虑过改用模拟器?

您可以让代码本身更加了解它正在处理的数据大小,例如:

printf("%hu\n", a - b);

来自 fprintf 的文档:

h

Specifies that a following d, i, o, u, x, or X conversion specifier applies to a short int or unsigned short int argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to short int or unsigned short int before printing);

您需要一个完整的运行时间环境,包括共享您正在实施的 ABI 所需的所有库。

如果您想 运行 在 32 位系统上运行您的 16 位代码,您最有可能成功的机会是 运行 在具有可比性 运行time 环境,如果你也需要 ISA 翻译,可能使用 qemu-user-static。也就是说,我不确定 QEMU 支持的任何平台是否具有 16 位 ABI。

可能自己编写一组 16 位 shim 库,由您平台的本机库支持 - 但我怀疑付出的努力会超过给你带来的好处。

请注意,对于在 64 位 amd64 主机上 运行ning 32 位 x86 二进制文件的特定情况,Linux 内核通常配置有双 ABI 支持(您仍然需要当然,合适的 32 位库)。