同じバイナリでも符号の有無で10進数は変わる
同じ0xFFFFFFFEでも符号ありと解釈するか無しと解釈するかで値が変わる。
#include <stdio.h>
int main() {
signed int a = 0xFFFFFFFE;
unsigned int b = 0xFFFFFFFE;
printf("signed a = %d\n", a);
printf("unsigned b = %u\n", b);
return 0;
}
testuser@CasaOS:~/test$ gcc test.c -o test
testuser@CasaOS:~/test$ ./test
signed a = -2
unsigned b = 4294967294
アセンブリで二つの値に差があるかを確認
変数格納
逆アセンブルの命令は以下の通り
gcc -O0 -S test.c -o test.s
signed int a = 0xFFFFFFFE;
unsigned int b = 0xFFFFFFFE;
以下のようなコードへ変換された。
変数へ値を格納する段階ではsigned intとunsigned intの差異は見られない。
mvn r3, #1
str r3, [r7, #4]
mvn r3, #1
str r3, [r7]
指定した値をビット反転(NOT)してレジスタに入れる。
#1は32ビットで00000000 00000000 00000000 00000001
反転させると11111111 11111111 11111111 11111110となる。
16進数で表すと0xFFFFFFFE
元のコード
#include <stdio.h>
int main() {
signed int a = 0xFFFFFFFE;
unsigned int b = 0xFFFFFFFE;
return 0;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.section .rodata
.align 2
.LC0:
.ascii "signed a = %d\012\000"
.align 2
.LC1:
.ascii "unsigned b = %u\012\000"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr}
sub sp, sp, #8
add r7, sp, #0
mvn r3, #1
str r3, [r7, #4]
mvn r3, #1
str r3, [r7]
ldr r1, [r7, #4]
ldr r3, .L3
.LPIC0:
add r3, pc
mov r0, r3
bl printf(PLT)
ldr r1, [r7]
ldr r3, .L3+4
.LPIC1:
add r3, pc
mov r0, r3
bl printf(PLT)
movs r3, #0
mov r0, r3
adds r7, r7, #8
mov sp, r7
@ sp needed
pop {r7, pc}
.L4:
.align 2
.L3:
.word .LC0-(.LPIC0+4)
.word .LC1-(.LPIC1+4)
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
加算命令 符号有無による違い
どちらもadds命令を使っており違いはない
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
// ...省略...
a = a + 11;
b = b + 22;
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
@ ...省略...
ldr r3, [r7, #12]
adds r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
adds r3, r3, #22
str r3, [r7, #8]
元のコード
int main() {
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
int signed_result, unsigned_result;
a = a + 11;
b = b + 22;
return signed_result + unsigned_result;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
ldr r3, [r7, #12]
adds r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
adds r3, r3, #22
str r3, [r7, #8]
ldr r2, [r7, #4]
ldr r3, [r7]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
減算命令 符号有無による違い
どちらもsubs命令を使っており違いはない
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
// ...省略...
a = a - 11;
b = b - 22;
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
@ ...省略...
ldr r3, [r7, #12]
subs r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
subs r3, r3, #22
str r3, [r7, #8]
元のコード
int main() {
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
int signed_result, unsigned_result;
a = a - 11;
b = b - 22;
return signed_result + unsigned_result;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
ldr r3, [r7, #12]
subs r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
subs r3, r3, #22
str r3, [r7, #8]
ldr r2, [r7, #4]
ldr r3, [r7]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
加算命令 符号有無による違い
どちらもadds命令を使っており違いはない
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
// ...省略...
a = a + 11;
b = b + 22;
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
@ ...省略...
ldr r3, [r7, #12]
adds r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
adds r3, r3, #22
str r3, [r7, #8]
元のコード
int main() {
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
int signed_result, unsigned_result;
a = a + 11;
b = b + 22;
return signed_result + unsigned_result;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
ldr r3, [r7, #12]
adds r3, r3, #11
str r3, [r7, #12]
ldr r3, [r7, #8]
adds r3, r3, #22
str r3, [r7, #8]
ldr r2, [r7, #4]
ldr r3, [r7]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
乗算命令 符号有無による違い
どちらもmul命令を使っており違いはない
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
// ...省略...
a = a * 11;
b = b * 22;
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
@ ...省略...
ldr r3, [r7, #12]
movs r2, #11
mul r3, r2, r3
str r3, [r7, #12]
ldr r3, [r7, #8]
movs r2, #22
mul r3, r2, r3
str r3, [r7, #8]
元のコード
int main() {
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
int signed_result, unsigned_result;
a = a * 11;
b = b * 22;
return signed_result + unsigned_result;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
ldr r3, [r7, #12]
movs r2, #11
mul r3, r2, r3
str r3, [r7, #12]
ldr r3, [r7, #8]
movs r2, #22
mul r3, r2, r3
str r3, [r7, #8]
ldr r2, [r7, #4]
ldr r3, [r7]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
除算命令 符号有無による違い
smullとumullを使い分けている。
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
// ...省略...
a = a / 11;
b = b / 22;
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
@ ...省略...
ldr r2, [r7, #12]
movw r3, #41705
movt r3, 11915
smull r1, r3, r3, r2
asrs r1, r3, #1
asrs r3, r2, #31
subs r3, r1, r3
str r3, [r7, #12]
ldr r2, [r7, #8]
movw r3, #35747
movt r3, 47662
umull r2, r3, r3, r2
lsrs r3, r3, #4
str r3, [r7, #8]
元のコード
int main() {
signed int a = 0xFFFFFFFE; // 2
unsigned int b = 0xFFFFFFFE; // 4294967294
int signed_result, unsigned_result;
a = a / 11;
b = b / 22;
return signed_result + unsigned_result;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mvn r3, #1
str r3, [r7, #12]
mvn r3, #1
str r3, [r7, #8]
ldr r2, [r7, #12]
movw r3, #41705
movt r3, 11915
smull r1, r3, r3, r2
asrs r1, r3, #1
asrs r3, r2, #31
subs r3, r1, r3
str r3, [r7, #12]
ldr r2, [r7, #8]
movw r3, #35747
movt r3, 47662
umull r2, r3, r3, r2
lsrs r3, r3, #4
str r3, [r7, #8]
ldr r2, [r7, #4]
ldr r3, [r7]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
比較命令
符号有りはbge、符号無しはbcs命令に変換された。
volatile signed int a = 0xFFFFFFFF;
volatile unsigned int b = 0xFFFFFFFF;
volatile signed int x = 0;
volatile unsigned int y = 0;
// ...省略...
if (a < x)
// ...省略...
if (b < x)
cmp r2, r3
bge .L2
@ ...省略...
cmp r3, r2
bcs .L4
元のコード
int main() {
volatile signed int a = 0xFFFFFFFF;
volatile unsigned int b = 0xFFFFFFFF;
volatile signed int x = 0;
volatile unsigned int y = 0;
// 符号あり比較
if (a < x) {
a = 1;
} else {
a = 2;
}
// 符号なし比較
if (b < x) {
b = 1;
} else {
b = 2;
}
return a + b;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
mov r3, #-1
str r3, [r7, #12]
mov r3, #-1
str r3, [r7, #8]
movs r3, #0
str r3, [r7, #4]
movs r3, #0
str r3, [r7]
ldr r2, [r7, #12]
ldr r3, [r7, #4]
cmp r2, r3
bge .L2
movs r3, #1
str r3, [r7, #12]
b .L3
.L2:
movs r3, #2
str r3, [r7, #12]
.L3:
ldr r3, [r7, #8]
ldr r2, [r7, #4]
cmp r3, r2
bcs .L4
movs r3, #1
str r3, [r7, #8]
b .L5
.L4:
movs r3, #2
str r3, [r7, #8]
.L5:
ldr r3, [r7, #12]
mov r2, r3
ldr r3, [r7, #8]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
大きい型へのキャスト(拡張)
このように拡張される。
ldrsh : 0xFFFF → 0xFFFFFFFF
ldrh : 65535 → 0x0000FFFF
short s = 0xFFFF;
unsigned short us = 0xFFFF;
int a = (int)s; // (符号拡張)
int b = (int)us; // 65535 → 0x0000FFFF (ゼロ拡張)
movw r3, #65535
strh r3, [r7, #14] @ movhi
movw r3, #65535
strh r3, [r7, #12] @ movhi
ldrsh r3, [r7, #14]
str r3, [r7, #8]
ldrh r3, [r7, #12]
str r3, [r7, #4]
元のコード
int main() {
short s = 0xFFFF;
unsigned short us = 0xFFFF;
int a = (int)s;
int b = (int)us;
return a + b;
}
.arch armv7-a
.fpu vfpv3-d16
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
push {r7}
sub sp, sp, #20
add r7, sp, #0
movw r3, #65535
strh r3, [r7, #14] @ movhi
movw r3, #65535
strh r3, [r7, #12] @ movhi
ldrsh r3, [r7, #14]
str r3, [r7, #8]
ldrh r3, [r7, #12]
str r3, [r7, #4]
ldr r2, [r7, #8]
ldr r3, [r7, #4]
add r3, r3, r2
mov r0, r3
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Debian 12.2.0-14+deb12u1) 12.2.0"
.section .note.GNU-stack,"",%progbits
加減乗除 符号有無で処理が変わらない理由
CPUは演算中の32ビット値が符号有りか符号無しかは知らない。
符号の有無は演算に関係無く、計算結果をどちらとして解釈するかによって変わる。
補数表現
例として-2を2進数で表す。
0x00000002 = 00000000 00000000 00000000 00000010
全ビット反転
00000000 00000000 00000000 00000010 → 11111111 11111111 11111111 11111101
0x00000002 → 0xFFFFFFFD
1を足す
0xFFFFFFFD + 1 = 0xFFFFFFFE
-2を2進数で表すと0xFFFFFFFEとなる
加算
0x00000002 + 0x00000003 = 0x00000005
0xFFFFFFFE + 0x00000003 = 0x00000001
| 計算式 | ビット列 | signed 解釈 | unsigned 解釈 |
|---|---|---|---|
| 0x00000002 + 0x00000003 | 0x00000005 | 5 | 5 |
| 0xFFFFFFFE + 0x00000003 | 0x00000001 | 1 | 1 |
減算
0x00000008 - 0x00000003 = 0x00000005
0xFFFFFFFE - 0x00000003 = 0xFFFFFFFB
| 計算式 | ビット列 | signed 解釈 | unsigned 解釈 |
|---|---|---|---|
| 0x00000008 - 0x00000003 | 0x00000005 | 5 | 5 |
| 0xFFFFFFFE - 0x00000003 | 0xFFFFFFFB | -5 | 4294967291 |
乗算
0x00000004 × 0x00000003 = 0x0000000C
0xFFFFFFFE × 0x00000003 = 0xFFFFFFFA
| 計算式 | ビット列 | signed 解釈 | unsigned 解釈 |
|---|---|---|---|
| 0x00000004 × 0x00000003 | 0x0000000C | 12 | 12 |
| 0xFFFFFFFE × 0x00000003 | 0xFFFFFFFA | -6 | 4294967290 |
符号有無の差異違い
| 操作 | ARM命令 / 実装 | 符号依存 |
|---|---|---|
| 加算 | adds |
なし |
| 減算 | subs |
なし |
| 乗算 | mul |
なし |
| 除算 |
sdiv / udiv
|
あり |
| 比較 |
cmp + 条件分岐 |
あり |
| 型変換 / キャスト |
movsx(符号拡張) / movzx(ゼロ拡張) |
あり |
動作環境(Linux / ARM 版)
OS: Armbian 23.08.0-trunk (Debian 12 Bookworm)
Kernel: Linux 6.1.38-meson (armv7l)
CPU: ARM Cortex-A5, 4 cores, Little Endian
GCC: 12.2.0
Architecture: armv7l
備考: Windows10からSSH接続して使用