У меня есть две сборки для части программного обеспечения, которую я разрабатываю, одна для встроенной системы, где размер 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;
}
EDIT:
Пример, который я привел, возможно, не был лучшим примером, но я действительно хочу, чтобы целочисленное продвижение было до 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;
}
Вывод 0
в 32-битной системе из-за усечения от целочисленного деления после перехода к (подписанному) целому, в отличие от 32767
.
Лучший ответ на данный момент, похоже, заключается в использовании эмулятора, на который я не надеялся, но я думаю, что он имеет смысл. Похоже, что для компилятора теоретически должно быть возможно сгенерировать код, который ведет себя так, как будто размер int равен 16 битам, но я думаю, что, возможно, не должно быть слишком удивительным, что на практике нет простого способа сделать это, и, вероятно, нет особого спроса на такой режим и какую-либо необходимую поддержку во время выполнения.
РЕДАКТИРОВАТЬ 2:
Это то, что я исследовал до сих пор: на самом деле существует версия GCC, которая нацелена на i386 в 16-битном режиме на 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-битный эльф в качестве промежуточного звена, хотя по умолчанию итоговая ссылка выводится в виде файла 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
, скомпилированным с обычным GCC, но неудивительно, что в результате получаются исполняемые ошибки segfaults.
$ 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)
Из поста здесь кажется, что теоретически должно быть возможно связать вывод 16-битного кода из ia16-elf-gcc с 32-битным кодом, хотя я не совсем уверен, как , Кроме того, существует проблема запуска 16-разрядного кода в 64-разрядной ОС. Более идеальным был бы компилятор, который все еще использует обычные 32-битные / 64-битные регистры и инструкции для выполнения арифметики, но эмулирует арифметику посредством библиотечных вызовов, аналогично тому, как, например, uint64_t
эмулируется на (не-64- немного) микроконтроллер.
Самое близкое, что я мог бы найти для фактического запуска 16-битного кода на x86-64, это здесь , и это кажется экспериментальным / полностью не поддерживаемым. На данный момент, просто использование эмулятора начинает казаться лучшим решением, но я подожду немного дольше и посмотрю, есть ли у кого-нибудь еще идеи.
РЕДАКТИРОВАТЬ 3
Я собираюсь пойти дальше и принять ответ Антти, хотя это не тот ответ, который я надеялся услышать. Если кому-то интересно узнать, что выводит 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, эта инструкция вызывает segfault
movl $0x46c70000,-0x2(%esi)
Это первые две команды перемещения для установки значений a и b, интерпретируемые с командой, декодированной в 32-битном режиме. Соответствующая разборка (без указания 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 в основном приводит к 16-битному реальному режиму x86), это может быть даже 16-битный защищенный режим.Но на данный момент использование эмулятора определенно кажется лучшим вариантом, и это больше для моего любопытства.Это все также характерно для x86.Для справки вот тот же файл, скомпилированный в 32-битном режиме, который имеет неявное продвижение до 32-битного со знаком int (от запуска 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