2026-05-06 22:46:08
Suwako
machine-language
low-level

【Machine Language】How to Program in Machine Language

Last year, I covered a lot about assembly language.
Programming in assembly language allows us to optimize performance and eliminate the compiler by sacrificing programming ease and portability.
But what about going one level lower than that?
What if we could also eliminate the assembler and linker?

Enter machine language.

Almost no one writes directly in machine language except for demoscene enthusiasts, low-level embedded developers, reverse engineers, and people who just can't wait.
However, it's good to know how the machine actually understands code, and it's extremely useful if you want to create your own assembler or linker.
In this experiment, we'll use Linux on an AArch64 machine (specifically the Raspberry Pi 5).

$ farfetch
    ___      suwako@crux-rpi5
   (.· |     ------------------
   (<> |     OS: Linux 6.12.25-v8-16k+ aarch64
  / __  \    Distro: CRUX-ARM64
 ( /  \ /|   Host: Raspberry Pi 5 Model B Rev 1.1
_/\ __)/_)   Uptime: 0 days, 46 mins
\/-____\/    Shell: zsh 5.9
             CPU:  @ GHz (4 core)
             Memory: 175 MiB / 8059 MiB
             Storage: /dev/root: 13G / 113G

Opcodes

First, we need to know the opcodes.
As I already explained last year, assembly language is simply a human-readable form of machine code.
Therefore, if you know AArch64 assembly, you technically already know AArch64 machine code.
Here are the opcodes:
movD280
adr1000
svcD400

Code

D2 80 0B A8
D2 80 05 40
D4 00 00 01

This is equivalent to the following:

  mov x8, #93
  mov x0, #42
  svc #0

However, this alone is still not enough.
To actually run it, we first need a proper header.

7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00
02 00 b7 00 01 00 00 00  78 00 40 00 00 00 00 00
40 00 00 00 00 00 00 00  a0 02 00 00 00 00 00 00
00 00 00 00 40 00 38 00  01 00 40 00 06 00 05 00
01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00
00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00
b5 00 00 00 00 00 00 00  b5 00 00 00 00 00 00 00
00 00 01 00 00 00 00 00  08 08 80 d2 20 00 80 d2
c1 00 00 10 a2 03 80 d2  01 00 00 d4 a8 0b 80 d2
40 05 80 d2 01 00 00 d4  e3 81 93 e3 82 93 e3 81
ab e3 81 a1 e3 81 af e3  80 81 e4 b8 96 e7 95 8c
ef bc 81 0a 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 03 00 01 00  78 00 40 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 03 00 02 00
98 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 04 00 f1 ff  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  08 00 00 00 00 00 02 00
98 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
0c 00 00 00 00 00 f1 ff  1d 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  13 00 00 00 00 00 01 00
78 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
25 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  16 00 00 00 10 00 02 00
b5 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
24 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  35 00 00 00 10 00 01 00
78 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
30 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  3c 00 00 00 10 00 02 00
b8 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
44 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  4b 00 00 00 10 00 02 00
b8 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
00 6d 61 69 6e 2e 6f 00  6d 73 67 00 6d 73 67 4c
65 6e 00 24 78 00 5f 5f  62 73 73 5f 73 74 61 72
74 5f 5f 00 5f 5f 62 73  73 5f 65 6e 64 5f 5f 00
5f 5f 62 73 73 5f 73 74  61 72 74 00 5f 5f 65 6e
64 5f 5f 00 5f 65 64 61  74 61 00 5f 65 6e 64 00
00 2e 73 79 6d 74 61 62  00 2e 73 74 72 74 61 62
00 2e 73 68 73 74 72 74  61 62 00 2e 74 65 78 74
00 2e 72 6f 64 61 74 61  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
1b 00 00 00 01 00 00 00  06 00 00 00 00 00 00 00
78 00 40 00 00 00 00 00  78 00 00 00 00 00 00 00
20 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
21 00 00 00 01 00 00 00  02 00 00 00 00 00 00 00
98 00 40 00 00 00 00 00  98 00 00 00 00 00 00 00
1d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  b8 00 00 00 00 00 00 00
68 01 00 00 00 00 00 00  04 00 00 00 07 00 00 00
08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00
09 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  20 02 00 00 00 00 00 00
50 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
11 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  70 02 00 00 00 00 00 00
29 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

If you don't have a hex editor, you can do it like this:

$ cat > elf.hex << 'EOF'
7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00
02 00 b7 00 01 00 00 00  78 00 40 00 00 00 00 00
40 00 00 00 00 00 00 00  a0 02 00 00 00 00 00 00
00 00 00 00 40 00 38 00  01 00 40 00 06 00 05 00
01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00
00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00
b5 00 00 00 00 00 00 00  b5 00 00 00 00 00 00 00
00 00 01 00 00 00 00 00  08 08 80 d2 20 00 80 d2
c1 00 00 10 a2 03 80 d2  01 00 00 d4 a8 0b 80 d2
40 05 80 d2 01 00 00 d4  e3 81 93 e3 82 93 e3 81
ab e3 81 a1 e3 81 af e3  80 81 e4 b8 96 e7 95 8c
ef bc 81 0a 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 03 00 01 00  78 00 40 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 03 00 02 00
98 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 04 00 f1 ff  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  08 00 00 00 00 00 02 00
98 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
0c 00 00 00 00 00 f1 ff  1d 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  13 00 00 00 00 00 01 00
78 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
25 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  16 00 00 00 10 00 02 00
b5 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
24 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  35 00 00 00 10 00 01 00
78 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
30 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  3c 00 00 00 10 00 02 00
b8 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
44 00 00 00 10 00 02 00  b5 00 41 00 00 00 00 00
00 00 00 00 00 00 00 00  4b 00 00 00 10 00 02 00
b8 00 41 00 00 00 00 00  00 00 00 00 00 00 00 00
00 6d 61 69 6e 2e 6f 00  6d 73 67 00 6d 73 67 4c
65 6e 00 24 78 00 5f 5f  62 73 73 5f 73 74 61 72
74 5f 5f 00 5f 5f 62 73  73 5f 65 6e 64 5f 5f 00
5f 5f 62 73 73 5f 73 74  61 72 74 00 5f 5f 65 6e
64 5f 5f 00 5f 65 64 61  74 61 00 5f 65 6e 64 00
00 2e 73 79 6d 74 61 62  00 2e 73 74 72 74 61 62
00 2e 73 68 73 74 72 74  61 62 00 2e 74 65 78 74
00 2e 72 6f 64 61 74 61  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
1b 00 00 00 01 00 00 00  06 00 00 00 00 00 00 00
78 00 40 00 00 00 00 00  78 00 00 00 00 00 00 00
20 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
21 00 00 00 01 00 00 00  02 00 00 00 00 00 00 00
98 00 40 00 00 00 00 00  98 00 00 00 00 00 00 00
1d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 02 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  b8 00 00 00 00 00 00 00
68 01 00 00 00 00 00 00  04 00 00 00 07 00 00 00
08 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00
09 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  20 02 00 00 00 00 00 00
50 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
11 00 00 00 03 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  70 02 00 00 00 00 00 00
29 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
EOF
$ xxd -r -p elf.hex > exe
$ chmod +x exe
$ ./exe
こんにちは、世界!

You can remove spaces and newlines, but since xxd removes them all internally, it makes no difference.
Having spaces and newlines makes it more readable.
Next, we can inspect the binary.

$ ls -thal exe
-rwxr-xr-x 1 suwako wheel 1008  5月  6 20:43 exe
$ file exe
exe: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, missing section headers at 992
$ readelf -h exe
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0x400078
  Start of program headers:          64 (bytes into file)
  Start of section headers:          672 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           64 (bytes)
  Number of section headers:         6
  Section header string table index: 5
readelf: Error: Reading 384 bytes extends past end of file for section headers
$ readelf -l exe
readelf -l exe
readelf: Error: Reading 384 bytes extends past end of file for section headers

Elf file type is EXEC (Executable file)
Entry point 0x400078
There is 1 program header, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000b5 0x00000000000000b5  R E    0x10000

Just 1008 bytes!
It became a little smaller compared to the same program written in assembly language (1.1 KiB).

The downside is that the binary is not in a 100% valid format, but it works.
The advantage is that by removing all padding, we can make the binary even smaller.
Removing the final 00 brings it down to 993 bytes.
Let's see how small we can make it.

7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00
02 00 b7 00 01 00 00 00  78 00 40 00 00 00 00 00
40 00 00 00 00 00 00 00  a0 02 00 00 00 00 00 00
00 00 00 00 40 00 38 00  01 00 40 00 06 00 05 00
01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00
00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00
b5 00 00 00 00 00 00 00  b5 00 00 00 00 00 00 00
00 00 01 00 00 00 00 00  08 08 80 d2 20 00 80 d2
c1 00 00 10 a2 03 80 d2  01 00 00 d4 a8 0b 80 d2
40 05 80 d2 01 00 00 d4  e3 81 93 e3 82 93 e3 81
ab e3 81 a1 e3 81 af e3  80 81 e4 b8 96 e7 95 8c
ef bc 81 0a 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 03 00 01 00  78 00 40 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 03 00 02 00
98 00 40 00 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 04 00 f1 ff  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  08 00 00 00 00 00 02 00
98 00 40 0c 00 00 00 00  00 f1 ff 1d 13 00 00 00
00 00 01 00 78 00 40 25  00 00 00 10 00 02 00 b5
00 41 16 00 00 00 10 00  02 00 b5 00 41 24 00 00
00 10 00 02 00 b5 00 41  35 00 00 00 10 00 01 00
78 00 40 30 00 00 00 10  00 02 00 b5 00 41 3c 00
00 00 10 00 02 00 b8 00  41 44 00 00 00 10 00 02
00 b5 00 41 4b b8 00 41  00 6d 61 69 6e 2e 6f 00
6d 73 67 00 6d 73 67 4c  65 6e 00 24 78 00 5f 5f
62 73 73 5f 73 74 61 72  74 5f 5f 00 5f 5f 62 73
73 5f 65 6e 64 5f 5f 00  5f 5f 62 73 73 5f 73 74
61 72 74 00 5f 5f 65 6e  64 5f 5f 00 5f 65 64 61
74 61 00 5f 65 6e 64 00  00 2e 73 79 6d 74 61 62
00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74
61 62 00 2e 74 65 78 74  00 2e 72 6f 64 61 74 61
1b 01 06 78 40 78 20 04  21 01 02 98 40 98 1d 01
01 02 b8 68 01 04 07 08  18 09 20 02 50 01 11 00
00 00 03 70 02 29 01

$ xxd -r -p elf.hex > exe
$ ./exe
こんにちは、世界!
$ ls -thal exe
-rwxr-xr-x 1 suwako wheel 551  5月  6 20:59 exe

It's already down to 551 bytes.
We should be able to make it even smaller.

7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00
02 00 b7 00 01 00 00 00  78 00 40 00 00 00 00 00
40 00 00 00 00 00 00 00  a0 02 00 00 00 00 00 00
00 00 00 00 40 00 38 00  01 00 40 00 06 00 05 00
01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00
00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00
b5 00 00 00 00 00 00 00  b5 00 00 00 00 00 00 00
00 00 01 00 00 00 00 00  08 08 80 d2 20 00 80 d2
c1 00 00 10 a2 03 80 d2  01 00 00 d4 a8 0b 80 d2
40 05 80 d2 01 00 00 d4  e3 81 93 e3 82 93 e3 81
ab e3 81 a1 e3 81 af e3  80 81 e4 b8 96 e7 95 8c
ef bc 81 0a 03 01 78 40  03 02 98 40 01 04 f1 ff
08 02 40 0c f1 ff 1d 13  01 78 40 25 10 02 b5 41
16 10 02 b5 41 24 10 02  b5 41 35 10 01 78 40 30
10 02 b5 41 3c 10 02 b8  41 44 10 02 b5 41 4b b8
41 6d 61 69 6e 2e 6f 6d  73 67 6d 73 67 4c 65 6e
24 78 5f 5f 62 73 73 5f  73 74 61 72 74 5f 5f 5f
5f 62 73 73 5f 65 6e 64  5f 5f 5f 5f 62 73 73 5f
73 74 61 72 74 5f 5f 65  6e 64 5f 5f 5f 65 64 61
74 61 5f 65 6e 64 2e 73  79 6d 74 61 62 2e 73 74
72 74 61 62 2e 73 68 73  74 72 74 61 62 2e 74 65
78 74 2e 72 6f 64 61 74  61 1b 01 06 78 40 78 20
04 21 01 02 98 40 98 1d  01 01 02 b8 68 01 04 07
08 18 09 20 02 50 01 11  03 70 02 29 01

$ xxd -r -p elf.hex > exe
$ ls -thal exe
-rwxr-xr-x 1 suwako wheel 381  5月  6 21:15 exe
$ file exe
exe: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, missing section headers at 992
$ ./exe
こんにちは、世界!

Now it's 381 bytes.
But can we make it smaller still?
The answer is "No".
If we reduce the padding any further, errors occur.
For example, deleting the 00s in the first 3.6 lines (up to 38 00 01 00 40) results in an exe format error.
Deleting the 00s from line 3.6 to line 6 causes a segmentation fault.
Deleting any other 00s results in an illegal hardware instruction.
Therefore, this is the final result.

Stripping the binary brings it down to 379 bytes, but it corrupts the string and continues to cause illegal hardware instruction errors, so we leave it without stripping.

Comparison with assembly language:

.section .rodata
.align 4
msg:
  .asciz "こんにちは、世界!\n"
  .equ msgLen, .-msg

.section .text
.align 4
.global _start

_start:
  mov x8, #64
  mov x0, #1
  adr x1, msg
  mov x2, msgLen
  svc #0

  mov x8, #93
  mov x0, #0
  svc #0

$ as main.s -o main.o
$ ld main.o -o main
$ ls -thal main
-rwxr-xr-x 1 suwako wheel 1.1K  5月  6 21:34 main
$ file main
main: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped
$ strip main
$ ls -thal main
-rwxr-xr-x 1 suwako wheel 472  5月  6 21:35 main
$ file main
main: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
$ ./main
こんにちは、世界!

In machine language: 381 bytes.
In assembly language: 472 bytes.
What about C?

#include <stdio.h>

int main() {
  printf("こんにちは、世界!\n");
  return 0;
}

$ cc --std=c23 main.c -o mainc -static
$ ls -thal mainc
-rwxr-xr-x 1 suwako wheel 616K  5月  6 21:39 mainc
$ file mainc
mainc: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]=c707cb647eedd3367b1e48859a084a0f81052388, for GNU/Linux 3.7.0, not stripped
$ strip mainc
$ ls -thal mainc
-rwxr-xr-x 1 suwako wheel 520K  5月  6 21:39 mainc
$ file mainc
mainc: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]=c707cb647eedd3367b1e48859a084a0f81052388, for GNU/Linux 3.7.0, stripped
$ ./mainc
こんにちは、世界!

In C, it became significantly larger at 520 KiB.
I compiled with both C99 and C23, but there was no difference between the two.
What about C++?
We'll test both C++11 and C++23.
C++23 finally introduced a more reasonable way to output text.

#include <iostream>

int main() {
  std::cout << "こんにちは、世界!" << std::endl;
  return 0;
}

#include <print>

int main() {
  std::print("こんにちは、世界!");
  return 0;
}

$ c++ --std=c++23 main11.cc -o maincpp11 -static
$ c++ --std=c++23 main23.cc -o maincpp23 -static
$ ls -thal maincpp11
-rwxr-xr-x 1 suwako wheel 2.2M  5月  6 21:44 maincpp11
$ ls -thal maincpp23
-rwxr-xr-x 1 suwako wheel 2.3M  5月  6 21:45 maincpp23
$ file maincpp11
maincpp11: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=48babdac130393529983d36cda30135899a3292b, for GNU/Linux 3.7.0, not stripped
$ file maincpp23
maincpp23: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=501c8926799fae121faeddbba6b33e3732bb83e1, for GNU/Linux 3.7.0, not stripped
$ strip maincpp11
$ strip maincpp23
$ ls -thal maincpp11
-rwxr-xr-x 1 suwako wheel 1.6M  5月  6 21:45 maincpp11
$ ls -thal maincpp23
-rwxr-xr-x 1 suwako wheel 1.7M  5月  6 21:45 maincpp23
$ file maincpp11
maincpp11: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=48babdac130393529983d36cda30135899a3292b, for GNU/Linux 3.7.0, stripped
$ file maincpp23
maincpp23: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=501c8926799fae121faeddbba6b33e3732bb83e1, for GNU/Linux 3.7.0, stripped
$ ./maincpp11
こんにちは、世界!
$ ./maincpp23
こんにちは、世界!

As expected, C++ became the largest so far.
I also tried compiling with C++99, but the result was the same as C++11.
The only difference was that the compilation time was significantly faster.
For comparison:

$ time c++ --std=c++98 main11.cc -o maincpp99 -static
c++ --std=c++98 main11.cc -o maincpp99 -static  0.41s user 0.06s system 99% cpu 0.464 total
$ time c++ --std=c++03 main11.cc -o maincpp03 -static
c++ --std=c++03 main11.cc -o maincpp03 -static  0.38s user 0.05s system 99% cpu 0.434 total

$ time c++ --std=c++11 main11.cc -o maincpp11 -static
c++ --std=c++11 main11.cc -o maincpp11 -static  0.56s user 0.08s system 99% cpu 0.647 total
$ time c++ --std=c++14 main11.cc -o maincpp14 -static
c++ --std=c++14 main11.cc -o maincpp14 -static  0.62s user 0.07s system 99% cpu 0.692 total
$ time c++ --std=c++17 main11.cc -o maincpp17 -static
c++ --std=c++17 main11.cc -o maincpp17 -static  0.73s user 0.05s system 99% cpu 0.781 total

$ time c++ --std=c++20 main11.cc -o maincpp20 -static
c++ --std=c++20 main11.cc -o maincpp20 -static  0.81s user 0.07s system 99% cpu 0.891 total
$ time c++ --std=c++23 main11.cc -o maincpp23 -static
c++ --std=c++23 main11.cc -o maincpp23 -static  1.72s user 0.11s system 99% cpu 1.822 total
$ time c++ --std=c++26 main11.cc -o maincpp26 -static
c++ --std=c++26 main11.cc -o maincpp26 -static  1.78s user 0.11s system 99% cpu 1.903 total


$ time c++ --std=c++23 main23.cc -o maincpp23 -static
c++ --std=c++23 main23.cc -o maincpp23 -static  2.47s user 0.12s system 99% cpu 2.590 total
$ time c++ --std=c++26 main23.cc -o maincpp26 -static
c++ --std=c++26 main23.cc -o maincpp26 -static  2.70s user 0.08s system 99% cpu 2.780 total

As you can see, only C++03 is faster to compile compared to the old standards.
Also, the new std::print seems to add significant overhead compared to std::cout.

As a bonus, let's try Zig too.

const std = @import("std");

pub fn main() void {
  std.debug.print("こんにちは、世界!\n", .{});
}

$ zig build-exe mainzig.zig -OReleaseSmall -fstrip -dead_strip
$ ls -thal mainzig
-rwxr-xr-x 1 suwako wheel 97K  5月  6 21:54 mainzig
$ file mainzig
mainzig: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
$ ./mainzig
こんにちは、世界!

Here is the final ranking:

  1. Machine language: 381 bytes
  2. Assembly language: 472 bytes
  3. Zig: 97 KiB
  4. C: 520 KiB
  5. C++ (using std::cout): 1.6 MiB
  6. C++ (using std::print): 1.7 MiB

That's all