对于正在开发的软件,我有两种版本,一种用于int大小为16位的嵌入式系统,另一种用于在int大小为32位的台式机上进行测试。我正在使用<stdint.h>
中的固定宽度整数类型,但是整数提升规则仍然取决于整数的大小。
理想情况下,由于整数提升,我希望使用类似下面的代码来打印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
,因为与32767
相对,提升为(有符号)int后从整数除法被截断了。
到目前为止,最好的答案似乎是使用仿真器,这不是我想要的,但是我认为确实是有道理的。从理论上来说,编译器似乎可以生成行为,就像int的大小为16位一样,但我想实践中没有简单的方法可能并不奇怪,对这种模式和任何必要的运行时支持的需求可能不大。
编辑2:
这是我到目前为止所探索的:实际上,有一个GCC版本以https://github.com/tkchia/gcc-ia16为目标,以16位模式的i386为目标。输出是一个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
再进一步研究一下,虽然默认情况下最终的链接输出是COM文件,但ia16-elf-gcc实际上确实会产生一个32位的elf作为中间变量。
$ 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
我可以强制它与使用常规GCC编译的main.c
链接,但并不奇怪,它会产生可执行的段错误。
$ 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)
从here帖子看来,从理论上讲应该可以将ia16-elf-gcc的16位代码输出链接到32位代码,尽管我实际上不确定如何做到。然后还有在64位OS上实际运行16位代码的问题。更理想的是,编译器仍然使用常规的32位/ 64位寄存器和指令执行算术运算,但是通过库调用来模拟算术运算,类似于在uint64_t
上模拟$ objdump -M intel -mi386 -Maddr16,data16 -S test16.o > test16.s
64位)微控制器。
在x86-64上实际运行16位代码的地方,我能找到的最接近的是here,这似乎是实验性的/完全没有维护。在这一点上,仅使用模拟器似乎是最好的解决方案,但是我将等待一会儿,看看是否有人有任何想法。
编辑3
我将继续接受antti的答案,尽管这不是我希望听到的答案。如果有人对ia16-elf-gcc的输出是什么感兴趣(我以前从未听说过ia16-elf-gcc),那么这里是反汇编:
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
请注意,您必须指定它是16位代码,否则objdump会将其解释为32位代码,它映射到不同的指令(请参阅下一节)。
movl $0x46c70000,-0x2(%esi)
在GDB中调试程序,此指令会导致段错误
$ 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
前两个移动指令用于设置用32位模式解码的指令解释的a和b的值。相关反汇编(未指定16位模式)如下:
gcc -m32 -c test16.c -o test16_32.o && objdump -M intel -S test16_32.o > test16_32.s
下一步将尝试找出一种将处理器置于16位模式的方法。它甚至不必是实模式(谷歌搜索通常会显示x86 16位实模式的结果),甚至可以是16位保护模式。但是在这一点上,使用仿真器绝对是最好的选择,而这更多是出于我的好奇心。所有这些也特定于x86。作为参考,以下是在32位模式下编译的同一文件,该文件隐式提升为32位带符号的int(通过运行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
):
{{1}}
答案 0 :(得分:19)
您不能,除非找到一些非常特殊的编译器。它将破坏所有内容,包括您的printf
通话。 32位编译器中的代码生成甚至可能 无法生成16位算术代码,因为通常不需要。
您是否考虑过使用仿真器?
答案 1 :(得分:6)
您需要一个完整的运行时环境,其中包括所有必需的库以共享您正在实现的ABI。
如果要在32位系统上运行16位代码,则最有可能获得成功的机会是在具有类似运行时环境的chroot中运行它,如果需要,可以使用qemu-user-static
ISA翻译也是如此。也就是说,我不确定QEMU支持的平台是否具有16位ABI。
有可能自己编写一套由平台的本地库支持的16位 shim 库-但我怀疑这样做的好处可能超过你。
请注意,对于在64位amd64主机上运行32位x86二进制文件的特定情况,Linux内核通常配置为具有双ABI支持(当然,您仍然需要适当的32位库)。 / p>
答案 2 :(得分:2)
例如,您可以使代码本身更了解其正在处理的数据大小:
printf("%hu\n", a - b);
摘自fprintf
的文档:
h
指定后面的d,i,o,u,x或X转换说明符适用于short int或unsigned short int参数(该参数将根据整数提升进行提升,但应转换其值在打印前将short int或unsigned short int缩短);