- 技術 💻
アセンブリ言語とは|機械語・アセンブラとの違いをNASMとgdbで実行
結論
アセンブリ言語は、CPU が実行する機械語(0と1の命令)を、mov・add のような短い英字に1対1で置き換えた言語です。アセンブリ言語を機械語に変換するソフトがアセンブラで、CPU の種類(x86-64・ARM64 など)ごとに書き方が違います。
この記事でわかること
- 機械語・アセンブラ・コンパイラの関係と、レジスタ・命令の読み方
- NASM と GNU as で書いた Hello World を実行し、gdb で1命令ずつ追った結果
- x86-64 と ARM64 の違い、手書きとコンパイラの速さの実測、エラー原文
プログラムは、人が書いたソースコードから、何段階かの変換を経て CPU が実行できる機械語になります。アセンブリ言語は、その途中にある「機械語を人が読める形にしたもの」です。
flowchart TD
C[C のソース sum.c] -->|コンパイラ gcc -S| S[アセンブリ言語<br>.s・.asm 手書きもここ]
S -->|アセンブラ nasm・as| O[オブジェクト .o<br>中身は機械語]
O -->|リンカ ld| E[実行ファイル]
E -.->|逆アセンブル objdump -d| S
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| アセンブリ言語とは | 機械語を英字の命令(ニーモニック)に置き換えた低水準言語 | アセンブリ言語とは |
| アセンブラとは | アセンブリ言語を機械語に変換するソフト(NASM・GNU as・MASM など) | アセンブリ言語とは |
| 機械語との違い | 中身は同じ命令。機械語は数値、アセンブリは文字で書く | 機械語とアセンブリ言語を並べて読む |
| C言語との違い | C はコンパイラがアセンブリを作る。gcc -S で見られる |
gcc -S で見る |
| 読み方(mov・add・cmp・jmp) | レジスタと命令の表を見ながら1行ずつ読む | レジスタと命令の読み方 |
| Hello World の書き方 | Linux なら write と exit のシステムコールを呼ぶ | NASM で Hello World |
| 手で書くと速いのか | 手元の実測では、コンパイラの -O2 のほうが速かった | 手書きとコンパイラの速さ |
| 今どこで使われているか | OS の入口・起動処理・組み込み・性能の調査・解析 | どこで使われているか |
確認環境:Ubuntu 24.04.5 LTS(x86-64、Intel Xeon 2.10GHz の2コア)で、NASM 2.16.01、GNU assembler・GNU ld・objdump 2.42(GNU Binutils for Ubuntu)、GCC 13.3.0、Ubuntu clang 18.1.3、GNU gdb 15.1、strace 6.8 を使いました。ARM64 は、Ubuntu のパッケージ binutils-aarch64-linux-gnu 2.42 と qemu-user 8.2.2 を apt-get download で取得して作業用のフォルダーに展開し、qemu-aarch64 でユーザーモード実行しています(ホストの binfmt などの設定は変えていません)。動かしたのは自分で書いたコードだけです。システムコールの呼び方は Linux の man ページ syscall(2)、AT&T 記法と Intel 記法の違いは GNU as のマニュアル、NASM の対応形式は NASM の公式ドキュメント、ARM64 のレジスタの役割は Arm の AAPCS64、gdb のコマンドは GDB のマニュアルで確かめました。Windows の MASM・Visual Studio、Mac での手順は扱っていません(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「アセンブリ言語とは|機械語・アセンブラとの違いをNASMとgdbで実行」
アセンブリ言語とは(機械語・アセンブラ・アセンブルの関係)
この節では、アセンブリ言語・機械語・アセンブラ・アセンブルという4つの言葉の関係と、高水準言語との位置づけが分かります。
アセンブリ言語(assembly language)は、CPU が直接実行する機械語の命令に、人が覚えやすい英字の名前を付けた言語です。 機械語の1命令が、アセンブリ言語のほぼ1行に対応します。どの命令があるか(命令セット)は CPU の種類ごとに決まっているため、x86-64 用のアセンブリは ARM64 では動きません。
- 機械語(マシン語):CPU が読む数値の列。例:
b8 01 00 00 00 - アセンブリ言語:機械語を文字で書いたもの。例:
mov eax, 1(eax に 1 を入れる) - ニーモニック:
mov・addのような命令の名前。対象(eax, 1の部分)はオペランドと呼ぶ - アセンブル:アセンブリ言語を機械語に変換すること
- アセンブラ:アセンブルするソフト。NASM、GNU as(gas)、Microsoft の MASM などがある
- 逆アセンブル:機械語をアセンブリ言語に戻すこと。
objdump -dや gdb が行う
アセンブリ言語(言語)
人が書く・読むための言葉
拡張子は .asm・.s・.S が多い
CPU の種類ごとに命令が違う
アセンブラ(ソフト)
アセンブリ言語を機械語に変換する道具
NASM・GNU as・MASM など
同じ CPU でもアセンブラごとに書き方が少し違う
会話では、アセンブリ言語そのものを「アセンブラ」と呼ぶこともあります。この記事では、言語をアセンブリ言語、変換するソフトをアセンブラと書き分けます。
高水準言語・コンパイラ・インタプリタとの位置づけ
| 種類 | 例 | 機械語への変換 | CPU の違い |
|---|---|---|---|
| 機械語 | b8 01 00 00 00 |
不要(そのまま実行) | CPU ごとに別物 |
| アセンブリ言語(低水準言語) | mov eax, 1 |
アセンブラがほぼ1対1で変換 | CPU ごとに書き直す |
| コンパイル型の高水準言語 | C・Rust・Go | コンパイラがまとめて変換(途中でアセンブリを作ることが多い) | コンパイルし直せば動く |
| インタプリタ型の高水準言語 | Python・Ruby | 実行時にインタプリタが読んで動かす | インタプリタがあれば動く |
コンパイラとインタプリタの違いは、関連記事「コンパイルとは|意味・インタプリタとの違いを6言語の実行で解説」で扱っています。
「アセンブリ」だけで検索したときの別の意味
「アセンブリ」は英語の assembly(組み立て・集まり)で、IT でも別の意味があります。.NET の「アセンブリ」は、Microsoft Learn によると「展開、バージョン管理、再利用、アクティブ化スコープ、およびセキュリティアクセス許可の基本的な単位」で、.exe や .dll のファイルとして作られます。 アセンブリ言語とは別物です。製造業では部品を組み立てた単位(サブアセンブリなど)を指します。
機械語とアセンブリ言語を並べて読む(objdump・nasm -l)
この節では、機械語の16進数とアセンブリ言語の1行が、実際にどう対応しているかが分かります。
機械語の数値を逆アセンブルすると、アセンブリ言語の命令が出てきます。 12バイトの数値をファイルに書き、objdump で x86-64 の命令として読ませました。
$ printf '\xb0\x61\xb1\x02\xb2\x03\x48\x89\xd8\x0f\x05\xc3' > raw.bin
$ od -An -tx1 raw.bin
b0 61 b1 02 b2 03 48 89 d8 0f 05 c3
$ objdump -D -b binary -m i386:x86-64 -M intel raw.bin
0: b0 61 mov al,0x61
2: b1 02 mov cl,0x2
4: b2 03 mov dl,0x3
6: 48 89 d8 mov rax,rbx
9: 0f 05 syscall
b: c3 ret
b0 61がmov al,0x61(AL というレジスタに 0x61 を入れる)でした。b0が「AL に即値を入れる」命令の番号(オペコード)、61が値です。b1・b2も同じmovですが、入れ先が CL・DL に変わるとオペコードが変わりました。同じmovという名前でも、オペランドの組み合わせで機械語は別の番号になります。- 命令の長さは2バイト、3バイト、1バイト(
retはc3の1バイト)とまちまちです。x86-64 は命令の長さが決まっていない CPU です。
反対に、アセンブラに「リスティング」を出させると、書いたアセンブリの横に、作られた機械語が並びます。NASM の -l オプションの出力です(後の節の Hello World)。
$ nasm -f elf64 hello.asm -l hello.lst
9 00000000 B801000000 mov rax, 1
10 00000005 BF01000000 mov rdi, 1
11 0000000A 488D35(00000000) lea rsi, [rel msg]
12 00000011 BA11000000 mov rdx, len
13 00000016 0F05 syscall
15 00000018 B83C000000 mov rax, 60
16 0000001D 31FF xor edi, edi
17 0000001F 0F05 syscall
左から行番号、先頭からの位置、機械語、元のアセンブリです。(00000000) はリンクするまで決まらないアドレスで、リンカが後で埋めます。16進数の読み方は、16進数の記事(16進数とは|変換の手順を図解・5言語で実行・早見表0〜255)で扱っています。ファイルを16進数で直接見る方法は、関連記事「バイナリエディタとは|使い方・おすすめとxxdで読む実例・戻し方」で扱っています。
C言語のアセンブリをgcc -Sで見る(-O0と-O2の比較)
この節では、C で書いた関数がどんなアセンブリになるのかと、最適化の有無でどれだけ変わるのかが分かります。
gcc -S を付けると、コンパイラは機械語を作る手前で止まり、アセンブリ言語のファイル(.s)を出力します。 1から n までを足す関数を用意しました。
int sum(int n) {
int s = 0;
for (int i = 1; i <= n; i++) {
s += i;
}
return s;
}
$ gcc -S -O0 -masm=intel -fno-asynchronous-unwind-tables sum.c -o sum_O0.s
-masm=intel は Intel 記法で出す指定、-fno-asynchronous-unwind-tables は例外処理用の .cfi_ の行を省いて読みやすくする指定です。最適化なし(-O0)の結果です(関数の部分だけ)。
sum:
endbr64
push rbp
mov rbp, rsp
mov DWORD PTR -20[rbp], edi ; 引数 n をスタックに置く
mov DWORD PTR -8[rbp], 0 ; s = 0
mov DWORD PTR -4[rbp], 1 ; i = 1
jmp .L2
.L3:
mov eax, DWORD PTR -4[rbp]
add DWORD PTR -8[rbp], eax ; s += i
add DWORD PTR -4[rbp], 1 ; i++
.L2:
mov eax, DWORD PTR -4[rbp]
cmp eax, DWORD PTR -20[rbp] ; i と n を比べる
jle .L3 ; i <= n なら .L3 へ戻る
mov eax, DWORD PTR -8[rbp] ; 戻り値 s を eax へ
pop rbp
ret
右のコメントは説明のために足したものです。-O0 では、変数 s・i を毎回メモリ(スタック)に読み書きしていて、C の1行がそのまま数行の命令になっています。 読む練習には -O0 が向いています。
-O2 と clang -O2 の結果
同じ C を GCC の -O2 と clang の -O2 でコンパイルし、objdump で数えました。
; GCC 13.3.0 -O2(ループの中だけ)
.L3:
lea edx, 1[rdx+rax*2] ; 2回分をまとめて足す
add eax, 2
cmp eax, ecx
jne .L3
; clang 18.1.3 -O2(関数全体)
sum:
test edi, edi
jle .LBB0_1
lea eax, [rdi - 1]
lea ecx, [rdi - 2]
imul rcx, rax
shr rcx
lea eax, [rcx + 2*rdi]
dec eax
ret
.LBB0_1:
xor eax, eax
ret
| コンパイル | 命令の数 | 機械語の大きさ | やっていること |
|---|---|---|---|
| GCC -O0 | 16 | 50バイト | C のとおり、メモリに読み書きしながら1ずつ足す |
| GCC -O2 | 21(詰め物の nop 2個を除く) | 69バイト | 変数をレジスタに置き、1回のループで2つずつ足す |
| clang -O2 | 11 | 26バイト | ループを消し、和の公式(掛け算とシフト)で一度に計算 |
clang は、ループを「n×(n+1)÷2」に相当する計算に置き換えていました。 C のソースは同じでも、コンパイラと最適化の段階でアセンブリはまったく違うものになります。速さの差は後の節で測っています。手元にコンパイラが無いときは、ブラウザでコンパイラの出力を見られる Compiler Explorer(godbolt.org)も使われています。外部のサイトなので、社外に出せないコードは貼らないようにします。
アセンブリ言語の読み方:レジスタと命令の早見表
この節では、x86-64 のアセンブリを読むのに最低限必要なレジスタ・命令・フラグが、表で分かります。
レジスタは CPU の中にある、とても速い小さな記憶場所です。 x86-64 では64ビットの汎用レジスタが16本あり、同じレジスタの下位32ビット・16ビット・8ビットにも別の名前が付いています。
| 64ビット | 32ビット | 16ビット | 8ビット | Linux での主な使われ方 |
|---|---|---|---|---|
| rax | eax | ax | al | 戻り値・システムコール番号 |
| rdi | edi | di | dil | 第1引数 |
| rsi | esi | si | sil | 第2引数 |
| rdx | edx | dx | dl | 第3引数 |
| rcx・r8・r9 | ecx・r8d・r9d | cx・r8w・r9w | cl・r8b・r9b | 関数の第4〜6引数(システムコールの第4引数は r10) |
| rbx・rbp・r12〜r15 | ebx・ebp・r12d〜 | bx・bp・r12w〜 | bl・bpl・r12b〜 | 呼ばれた側が値を保つレジスタ(rbp はフレームの基準にも使う) |
| rsp | esp | sp | spl | スタックの一番上を指す |
| rip | (eip) | 次に実行する命令のアドレス |
32ビットのレジスタ(eax など)に書くと、64ビットの上位32ビットは0になります。NASM が mov rax, 1 を5バイトの mov eax, 1 に縮めて機械語にしていたのは、この決まりで結果が同じになるためです(次の節のリスティング)。
よく出る命令
| 命令 | 意味 | Intel 記法の例 | C で書くと |
|---|---|---|---|
| mov | 値をコピーする | mov eax, 5 |
eax = 5 |
| lea | アドレスの計算結果を入れる(足し算にも使われる) | lea eax, [rdi-1] |
eax = rdi - 1 |
| add・sub | 足す・引く | add eax, ecx |
eax += ecx |
| inc・dec | 1増やす・1減らす | inc ecx |
ecx++ |
| imul・shr | 掛け算・右シフト(2で割る) | shr rcx, 1 |
rcx >>= 1 |
| xor | 排他的論理和。xor eax, eax は0にする定番 |
xor eax, eax |
eax = 0 |
| cmp・test | 比べて結果をフラグに残す(値は変えない) | cmp ecx, edi |
if (ecx ? edi) の準備 |
| jmp | 無条件に飛ぶ | jmp .loop |
goto |
| je・jne・jg・jl・jle | フラグを見て条件付きで飛ぶ(等しい・違う・大きい・小さい・以下) | jle .loop |
if (a <= b) goto |
| push・pop | スタックに積む・取り出す | push rbp |
|
| call・ret | 関数を呼ぶ(戻り先を積む)・戻る | call sum_to |
sum_to() |
| syscall | OS(カーネル)の機能を呼ぶ | syscall |
write() などの中身 |
フラグ(ZF・CF・SF・OF)
cmp a, b は内部で a−b を計算し、結果の性質をフラグというビットに残します。jle などの条件ジャンプは、このフラグを見て飛ぶかどうかを決めます。eax に 5 を入れて比べ、gdb の print $eflags で見た結果です。
| 命令 | 計算 | gdb の表示 | 読み取れること |
|---|---|---|---|
cmp eax, 5 |
5−5=0 | [ PF ZF IF ] |
ZF(ゼロ)が立つ → je で飛ぶ |
cmp eax, 7 |
5−7=−2 | [ CF AF SF IF ] |
SF(負)と CF(符号なしで借り)が立つ → jl・jb で飛ぶ |
cmp eax, 3 |
5−3=2 | [ IF ] |
どれも立たない → jg で飛ぶ |
add eax, 1(eax=0x7fffffff) |
符号付き最大値+1 | [ PF AF SF IF OF ] |
OF(あふれ)が立ち、eax は −2147483648 になった |
IF は割り込みを受け付ける状態を表すフラグで、比べた結果とは関係なく立っています。C の int のあふれは、CPU の上では OF が立つだけで止まらずに続きます。
Intel記法とAT&T記法の違い
この節では、同じ x86-64 の命令でも書き方が2種類ある理由と、読み替え方が分かります。
x86 のアセンブリには、Intel 記法と AT&T 記法があります。 NASM と MASM・Intel のマニュアルは Intel 記法、GNU as(GCC が出す .s)と objdump の既定は AT&T 記法です。GNU as のマニュアルにある違いを、手元の出力で並べました。
| 項目 | Intel 記法 | AT&T 記法 |
|---|---|---|
| オペランドの順 | 入れ先, 元:mov rbp, rsp |
元, 入れ先:movq %rsp, %rbp |
| 即値 | そのまま:mov eax, 1 |
$ を付ける:movl $1, %eax |
| レジスタ | そのまま:rax |
% を付ける:%rax |
| サイズ | メモリ側に書く:DWORD PTR |
命令の末尾に付ける:b・w・l・q(8・16・32・64ビット) |
| メモリ参照 | [rbp-4] |
-4(%rbp) |
| コメント | ;(NASM) |
#(x86 の GNU as) |
| 切り替え | gcc -masm=intel・objdump -M intel |
GNU as の中では .att_syntax・.intel_syntax noprefix |
-O0 の同じ1行を両方で出すと、次のようになりました。
AT&T :addl %eax, -8(%rbp)
Intel:add DWORD PTR -8[rbp], eax
どちらも「スタックの rbp−8 の位置にある32ビットの値に eax を足す」です。読むときは、まず % の有無で記法を見分け、AT&T なら右側が入れ先と覚えます。 GCC が出す Intel 記法では -8[rbp] と書かれますが、NASM では [rbp-8] と書く、という細かい違いもあります。
NASMでHello World(システムコールで書いて実行)
この節では、C のライブラリを使わずに、アセンブリ言語だけで文字を表示するプログラムの書き方・アセンブル・リンク・実行の手順が分かります。
Linux の x86-64 では、rax にシステムコールの番号、rdi・rsi・rdx に引数を入れて syscall 命令を実行すると、カーネルの機能を呼べます。 文字の表示は write(1番)、終了は exit(60番)です。
; hello.asm ― Linux x86-64 で write と exit のシステムコールだけを使う
section .data
msg: db "Hello, assembly!", 10 ; 10 は改行(LF)
len: equ $ - msg ; 文字列の長さ(17)
section .text
global _start
_start:
mov rax, 1 ; システムコール番号 1 = write
mov rdi, 1 ; 第1引数:ファイル記述子 1(標準出力)
lea rsi, [rel msg] ; 第2引数:文字列の先頭アドレス
mov rdx, len ; 第3引数:バイト数
syscall
mov rax, 60 ; システムコール番号 60 = exit
xor edi, edi ; 終了コード 0
syscall
section .data・section .text:データと命令を置く場所(セクション)を分ける指示です。db・equ・global:CPU の命令ではなく、アセンブラへの指示(疑似命令・ディレクティブ)です。$ - msgは「今の位置 − msg の位置」で、文字列の長さをアセンブラに計算させています。msg:・_start::ラベル。アドレスに名前を付けます。_startはリンカ(ld)が既定で探す入口の名前です。[rel msg]:今の命令からの距離(rip 相対)で msg を指す書き方です。
アセンブルする
nasm -f elf64 hello.asm -o hello.o。-f elf64 は Linux の64ビット用の形式(ELF)で出す指定。できた hello.o は896バイト。
リンクする
ld hello.o -o hello。アドレスを決めて実行ファイルにする。C のライブラリを使わないので gcc ではなく ld で足りる形になる。
実行して終了コードを見る
./hello; echo $?。文字が表示され、終了コードは 0 になった。
システムコールを確かめる
strace ./hello。プログラムが OS に頼んだことが一覧で見える。
$ nasm -f elf64 hello.asm -o hello.o
$ ld hello.o -o hello
$ ./hello; echo "終了コード: $?"
Hello, assembly!
終了コード: 0
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ strace ./hello
execve("./hello", ["./hello"], 0x7ffefa398bc0 /* 230 vars */) = 0
write(1, "Hello, assembly!\n", 17Hello, assembly!
) = 17
exit(0) = ?
+++ exited with 0 +++
strace に出たのは、起動の execve のほかは、書いたとおりの write と exit だけでした。 同じ表示を C の puts で書いたプログラムと比べました。
| 作り方 | ファイルの大きさ | システムコール(strace -c) |
|---|---|---|
| NASM+ld | 8,880バイト(strip 後 8,488) | 2回 |
NASM+ld -n -s(ページ境界にそろえない) |
4,560バイト | 2回 |
| C(gcc -O2、動的リンク) | 15,960バイト(strip 後 14,472) | 35回 |
| C(gcc -O2 -static) | 785,384バイト | 17回 |
C 版のシステムコールが多いのは、C のライブラリを読み込んで準備する処理があるためです。ファイルの大きさの多くは、命令ではなく、ELF の見出しとページ境界(4,096バイト)にそろえるための空きでした。ld -n では「LOAD segment with RWX permissions」(書き込みと実行を同時に許す領域がある)という警告が出たため、ふだんは付けないほうが安全です。
同じものを GNU as(AT&T 記法)で書く
GCC と一緒に入っている GNU as でも書けます。中身は同じで、記法が AT&T になります。
# hello_att.s ― 同じ処理を GNU as(AT&T 記法)で
.section .data
msg: .ascii "Hello, GNU as!\n"
len = . - msg
.section .text
.globl _start
_start:
movq $1, %rax # write
movq $1, %rdi # 標準出力
leaq msg(%rip), %rsi # 文字列のアドレス
movq $len, %rdx # バイト数
syscall
movq $60, %rax # exit
xorl %edi, %edi # 終了コード 0
syscall
$ as hello_att.s -o hello_att.o && ld hello_att.o -o hello_att && ./hello_att
Hello, GNU as!
$ objdump -d hello_att
401000: 48 c7 c0 01 00 00 00 mov $0x1,%rax
401007: 48 c7 c7 01 00 00 00 mov $0x1,%rdi
...
同じ mov rax, 1 でも、NASM は5バイトの b8 01 00 00 00、GNU as は7バイトの 48 c7 c0 01 00 00 00 に変換しました。 実行結果は同じです。どの機械語を選ぶかはアセンブラによって違うことがあります。
1から100の和を書く(ループ・比較・call/retとスタック)
この節では、ループと条件分岐、関数の呼び出し(call・ret)をアセンブリでどう書くかと、そのときスタックで何が起きているかが分かります。
ループは「足す → 比べる → 条件付きジャンプで戻る」の3つで書きます。 和を関数 sum_to にし、結果を終了コードとして返すようにしました。
; 1 から n までの和を求め、終了コードとして返す
section .text
global _start
_start:
mov edi, 100 ; n = 100
call sum_to ; 結果は eax に入る
mov edi, eax ; exit の第1引数 = 結果
mov eax, 60 ; exit
syscall
sum_to: ; eax = 1 + 2 + ... + edi
xor eax, eax
mov ecx, 1
.loop: add eax, ecx
inc ecx
cmp ecx, edi
jle .loop
ret
$ nasm -f elf64 sum100.asm -o sum100.o && ld sum100.o -o sum100 && ./sum100; echo "終了コード: $?"
終了コード: 186
(n を 10 に変えて作り直す)
終了コード: 55
n=100 の和は 5050 ですが、終了コードは 186 でした。Linux の終了コードは0〜255の8ビットで、5050 を256で割った余りの186だけが残ったためです。 n=10 なら和の55がそのまま出ます。大きな値を確かめるときは、後の節のように C から呼ぶか、gdb でレジスタを見ます。.loop のように点で始まるラベルは、NASM では直前のラベル(sum_to)の中だけで使える名前になります。
call と ret でスタックがどう動くか
gdb で call の前後のスタックポインタ(rsp)と、積まれた値を見ました。
6 call sum_to ; 結果は eax に入る
$1 = (void *) 0x7fffffff6ad0 ← call の前の rsp
sum_to () at sum100.asm:12
$2 = (void *) 0x7fffffff6ac8 ← call の後。8バイト減った
0x7fffffff6ac8: 0x000000000040100a ← 積まれた値
_start + 10 in section .text ← 戻り先(call の次の命令)
18 ret
$3 = 55 ← ret の直前の eax
_start () at sum100.asm:7
$4 = (void *) 0x7fffffff6ad0 ← ret の後。rsp が元に戻った
$5 = (void (*)()) 0x40100a <_start+10>
call は「次の命令のアドレス」をスタックに積んでから飛び、ret はそれを取り出して戻ります。 関数の中で push と pop の数が合わないと、ret が戻り先ではない値を取り出してしまい、プログラムが落ちます。
Cからアセンブリの関数を呼ぶ(呼び出し規約とインラインアセンブラ)
この節では、C とアセンブリを組み合わせるときの決まり(呼び出し規約)と、C の中に直接アセンブリを書く方法が分かります。
Linux の x86-64 では、関数の整数の引数は rdi・rsi・rdx・rcx・r8・r9 の順に渡し、戻り値は rax に入れる決まり(System V の呼び出し規約)です。 C の int sum(int n) と同じ形の関数を NASM で書き、C から呼びました。
; int sum(int n) を手で書いたもの(引数は edi、戻り値は eax)
global sum
section .text
sum:
xor eax, eax ; s = 0
mov ecx, 1 ; i = 1
.loop:
cmp ecx, edi ; i と n を比べる
jg .done ; i > n なら終わり
add eax, ecx ; s += i
inc ecx ; i++
jmp .loop
.done:
ret
section .note.GNU-stack noalloc noexec nowrite progbits
#include <stdio.h>
int sum(int n); /* 中身はアセンブリ */
int main(void){
printf("sum(10) = %d\n", sum(10));
printf("sum(100) = %d\n", sum(100));
return 0;
}
$ nasm -f elf64 sum_nasm.asm -o sn.o
$ gcc main.c sn.o -o main_asm && ./main_asm
sum(10) = 55
sum(100) = 5050
最後の section .note.GNU-stack の行は「スタックを実行可能にしなくてよい」という印です。この行が無いと、リンクのときに次の警告が出ました。
/usr/bin/ld: warning: sn.o: missing .note.GNU-stack section implies executable stack
/usr/bin/ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
rbx・rbp・r12〜r15 は「呼ばれた側が元の値を保つ」レジスタなので、関数の中で使うなら最初に push して最後に pop します。上の関数は rax・rcx・rdi しか触らないため、保存していません。
インラインアセンブラ(GCC の __asm__)
C のソースの中にアセンブリを書くこともできます。GCC の拡張アセンブリの書き方で、40と2を足しました。
#include <stdio.h>
int main(void) {
int a = 40, b = 2, r;
__asm__ ("movl %1, %0\n\t"
"addl %2, %0"
: "=&r"(r) /* 出力:r をレジスタに */
: "r"(a), "r"(b)); /* 入力:a と b をレジスタに */
printf("40 + 2 = %d\n", r);
return 0;
}
$ gcc -O2 inline.c -o inline && ./inline
40 + 2 = 42
%0・%1・%2 にどのレジスタを使うかはコンパイラが決めます。どのレジスタを壊すかをコンパイラに正しく伝えないと、最適化と組み合わさって誤動作するため、SIMD などの特別な命令を使いたいだけなら、コンパイラが用意している組み込み関数(intrinsics)のほうが安全です。
gdbでアセンブリをステップ実行する(レジスタ・メモリを見る)
この節では、gdb でアセンブリを1命令ずつ実行し、レジスタとメモリの変化を確かめる手順が分かります。
gdb の stepi(略して si)は、機械語の命令を1つだけ実行して止まります。 GDB のマニュアルは、関数呼び出しを1つとして飛ばす nexti(ni)も用意しています。NASM に -g -F dwarf を付けてデバッグ情報を入れ、Hello World を追いました。
$ nasm -f elf64 -g -F dwarf hello.asm -o hello_g.o && ld hello_g.o -o hello_g
$ gdb -q ./hello_g
(gdb) set disassembly-flavor intel
(gdb) break _start
(gdb) run
Breakpoint 1, _start () at hello.asm:9
9 mov rax, 1 ; システムコール番号 1 = write
(gdb) info registers rax rdi rsi rdx rip
rax 0x0 0
rdi 0x0 0
rsi 0x0 0
rdx 0x0 0
rip 0x401000 0x401000 <_start>
(gdb) stepi 4
13 syscall
(gdb) info registers rax rdi rsi rdx rip
rax 0x1 1
rdi 0x1 1
rsi 0x402000 4202496
rdx 0x11 17
rip 0x401016 0x401016 <_start+22>
(gdb) x/17xb $rsi
0x402000 <msg>: 0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x61
0x402008: 0x73 0x73 0x65 0x6d 0x62 0x6c 0x79 0x21
0x402010: 0x0a
(gdb) stepi
Hello, assembly!
15 mov rax, 60 ; システムコール番号 60 = exit
(gdb) info registers rax
rax 0x11 17
- 4命令進めたところで、rax=1(write)、rdi=1、rsi=文字列のアドレス、rdx=17 がそろい、
syscallの直前になりました。 x/17xb $rsiは「rsi が指すメモリを17バイト、16進数で」の意味です。0x48 が「H」、最後の 0x0a が改行です。- syscall の後、rax は 17 に変わりました。 write の戻り値(書けたバイト数)が rax に入るためです。
x/s $rsiで文字列として表示すると、末尾に「,」が付いた"Hello, assembly!\n,"になりました。x/s は値0のバイトまで読むため、0 で終わっていない文字列では後ろのバイトまで表示されます。
| gdb のコマンド | していること |
|---|---|
set disassembly-flavor intel |
逆アセンブルを Intel 記法で表示する |
break _start・break 6 |
ラベルや行番号で止める |
stepi(si)・nexti(ni) |
1命令進む・call は中に入らずに進む |
info registers(i r) |
レジスタの値を表示する |
print $eflags |
フラグを表示する |
x/8xb アドレス・x/s・x/3i $rip |
メモリを16進数・文字列・命令として表示する |
disassemble・display/i $pc |
今の関数を逆アセンブルする・止まるたびに次の命令を表示する |
C のプログラムでも、disassemble と stepi を使うと、どの命令で値がおかしくなったかを追えます。デバッグの進め方全体は、関連記事「デバッグとは|意味・手順・方法をPython・JS・Javaの実例で解説」で扱っています。
x86-64とARM64のアセンブリの違い(クロスアセンブラとqemu-user)
この節では、CPU が違うとアセンブリ言語がどう変わるのかを、ARM64 で同じ Hello World を書いて動かした結果で確かめられます。
スマートフォンや Apple シリコンの Mac、多くのサーバーで使われている ARM64(AArch64)は、x86-64 とは命令もレジスタも違います。 x86-64 のパソコンの上で ARM64 用の機械語を作るアセンブラ(クロスアセンブラ)と、ARM64 の機械語を x86-64 の上で動かす qemu-user を使いました。
// hello_arm64.s ― ARM64(AArch64)Linux 版
.data
msg: .ascii "Hello, ARM64!\n"
len = . - msg
.text
.globl _start
_start:
mov x0, #1 // 第1引数:標準出力
adr x1, msg // 第2引数:文字列のアドレス
mov x2, #len // 第3引数:バイト数
mov x8, #64 // システムコール番号 64 = write
svc #0
mov x0, #0 // 終了コード 0
mov x8, #93 // システムコール番号 93 = exit
svc #0
$ aarch64-linux-gnu-as hello_arm64.s -o hello_arm64.o
$ aarch64-linux-gnu-ld hello_arm64.o -o hello_arm64
$ file hello_arm64
hello_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped
$ ./hello_arm64
bash: line 1: ./hello_arm64: cannot execute binary file: Exec format error
$ qemu-aarch64 ./hello_arm64
Hello, ARM64!
$ qemu-aarch64 -strace ./hello_arm64
3643 write(1,0x4100d0,14)Hello, ARM64!
= 14
3643 exit(0)
$ aarch64-linux-gnu-objdump -d hello_arm64
4000b0: d2800020 mov x0, #0x1
4000b4: 100800e1 adr x1, 4100d0 <msg>
4000b8: d28001c2 mov x2, #0xe
4000bc: d2800808 mov x8, #0x40
4000c0: d4000001 svc #0x0
4000c4: d2800000 mov x0, #0x0
4000c8: d2800ba8 mov x8, #0x5d
4000cc: d4000001 svc #0x0
x86-64 のパソコンで直接実行すると「Exec format error」になり、qemu-aarch64 を通すと動きました。ARM64 の命令は、どれも4バイトちょうどでした。 x86-64 の /usr/bin/ls を逆アセンブルして命令の長さを数えると、20,764命令が1〜11バイトに散らばっていました(3バイトが4,960個で最多、1バイトが805個、11バイトが91個)。
同じ Hello World の書き方の違い
syscall で呼ぶ。write は1番、exit は60番。命令は1〜15バイトの可変長svc #0 で呼ぶ。write は64番、exit は93番。命令は4バイトの固定長共通:やっていることは同じ。CPU と OS の決まりに合わせて書き直す
C の sum を clang で ARM64 向けにコンパイルした結果です(clang --target=aarch64-linux-gnu -O2 -S)。
sum:
subs w8, w0, #1
b.lt .LBB0_2
sub w9, w0, #2
umull x8, w8, w9
lsr x8, x8, #1
add w8, w8, w0, lsl #1
sub w0, w8, #1
ret
.LBB0_2:
mov w0, wzr
ret
x86-64 版と同じく、ループが掛け算とシフトに置き換わっています。ARM64 では、引数と戻り値は x0〜x7(32ビットとして使うときは w0〜w7)、戻り先は x30(リンクレジスタ)に入ります(Arm の AAPCS64)。wzr は常に0を返す特別なレジスタです。-O0 では ldr(メモリから読む)と str(メモリに書く)が並び、x86-64 のように「メモリの値に直接足す」命令が無いことが分かります。命令セットの考え方の違い(RISC と CISC)や RISC-V との比較は、RISC-V の記事(RISC-Vとは|ARM・x86との違いを逆アセンブルとqemuで実測)で扱っています。
手書きのアセンブリはコンパイラより速いか(実測)
この節では、「アセンブリで書けば速くなる」が今も当てはまるのかを、同じ関数を4通りで作って測った結果で確かめられます。
手元の実測では、素直に手で書いたアセンブリは GCC -O2 より遅く、clang -O2 には桁違いで負けました。 1から50,000までの和を求める sum(50000) を4万回呼んだ時間です(GCC -O2 で作った同じ呼び出し側から、関数だけ差し替えています。3回測り、最小〜最大を書いています)。
| sum の作り方 | 4万回の時間 | 結果 | 中身 |
|---|---|---|---|
| C を GCC -O0 | 7.14〜7.78秒 | 1250025000 | 毎回メモリに読み書き |
| NASM で手書き(前の節の sum) | 1.48〜1.96秒 | 1250025000 | レジスタだけで1ずつ足す |
| C を GCC -O2 | 0.75〜1.25秒 | 1250025000 | 1回のループで2つずつ足す |
| C を clang -O2 | 0.00006〜0.00008秒 | 1250025000 | ループを消して公式で計算 |
2コアの共有の仮想マシンで測ったため、値には揺れがあります。それでも順番は3回とも同じでした。
1-O0 と比べると手書きは速い
-O0 は変数を毎回メモリに置くため、レジスタだけで書いた手書き版のほうが約4倍速かった。
2-O2 には負けた
GCC はループを2回分ずつ回す形に変えていた。素直な手書きでは、この工夫まで思いつきにくい。
3いちばん効くのは計算の仕方
clang は計算の方法そのものを変えた。速くしたいときは、命令を書く前にアルゴリズムと最適化オプションを見直す。
アセンブリを読めると、「コンパイラが何をしてくれたか」を自分の目で確かめられます。 速さが必要な部分でも、まず -O2 とアルゴリズムで速くし、それでも足りない処理だけをアセンブリで確かめる、という順番が無駄の少ない進め方です。CPU のキャッシュや分岐予測が速さにどう効くかは、関連記事「CPUの気持ち8選|分岐予測・キャッシュライン・高速化」で扱っています。
アセンブリでよくあるエラーと直し方(Segmentation fault ほか原文)
この節では、アセンブル・リンク・実行のそれぞれで出るエラーの原文と、原因・直し方が分かります。
アセンブリで最も多いのは、終了の処理を書き忘れて「Segmentation fault」で落ちる失敗です。 write だけ書いて exit を書かなかったプログラムを gdb で動かしました。
$ ./noexit; echo "終了コード: $?"
/bin/bash: line 11: 2920 Segmentation fault ./noexit
終了コード: 139
$ gdb -q -batch -ex run -ex 'x/3i $rip' ./noexit
Program received signal SIGSEGV, Segmentation fault.
0x000000000040100a in ?? ()
=> 0x40100a: add %al,(%rax)
0x40100c: add %al,(%rax)
0x40100e: add %al,(%rax)
CPU は、書いた命令の終わりで止まらず、その先のメモリ(値0のバイトが続く場所)を命令として読み続けました。00 00 は add %al,(%rax) という命令で、rax が1(アドレス1)のメモリに書こうとして落ちています。アセンブリには「関数の終わりで自動的に戻る」仕組みが無く、exit か ret を必ず自分で書きます。 終了コードの139は、128+11(SIGSEGV の番号)です。
| 出た文言(原文) | 原因 | 直し方 |
|---|---|---|
Segmentation fault(終了コード139) |
exit を書いていない・push と pop が合わず ret が変な所へ戻った・読めないアドレスを触った | gdb で落ちた命令(x/3i $rip)とレジスタを見る |
error: operation size not specified(NASM) |
mov [rbx], 1 のように、メモリに書く大きさが分からない |
mov dword [rbx], 1 のように大きさを書く |
error: invalid combination of opcode and operands(NASM) |
mov eax, rbx のように32ビットと64ビットを混ぜた |
大きさをそろえる(mov rax, rbx) |
error: symbol `mesage' not defined(NASM) |
ラベル名の書き間違い | ラベルの綴りを合わせる |
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 |
入口のラベルが _start になっていない・global を書いていない |
global _start と _start: を書く |
ld: i386 architecture of input file `e5.o' is incompatible with i386:x86-64 output |
32ビット形式(-f elf32)でアセンブルした |
nasm -f elf64 にする |
Error: operand size mismatch for `mov'(GNU as) |
AT&T 記法のファイルに Intel 記法の mov rax, 1 を書いた |
movq $1, %rax と書くか、先頭に .intel_syntax noprefix |
Error: incorrect register `%rax' used with `l' suffix(GNU as) |
32ビットの l で64ビットのレジスタを使った |
movq にするか %eax にする |
relocation R_X86_64_32 against `.data' can not be used when making a PIE object; recompile with -fPIE |
gcc(既定で PIE)でリンクするのに、mov esi, msg のように絶対アドレスを使った |
lea rsi, [rel msg] にする(または gcc -no-pie) |
missing .note.GNU-stack section implies executable stack |
NASM のファイルにスタックの印が無い | section .note.GNU-stack noalloc noexec nowrite progbits を足す |
cannot execute binary file: Exec format error |
別の CPU(ARM64 など)用の実行ファイルを動かした | file で形式を確かめ、その CPU の環境か qemu で動かす |
つまずきやすい書き方
int 0x80 と32ビットのシステムコール番号を、64ビットのプログラムにそのまま写すsyscall 命令と64ビット用の番号(write=1、exit=60)を使う。資料がどの CPU・OS・ビット数向けかを最初に確かめるWindows・macOS・Linux でもシステムコールの決まりは違う
以前の32ビット(i386)の Linux では、システムコールを int $0x80 で呼び、番号も別の表でした(man ページ syscall(2) の表)。ネットで見つけた例が動かないときは、まず対象の CPU・OS・ビット数を確かめます。
アセンブリ言語はどこで使われているか・メリットとデメリット
この節では、アセンブリ言語が今どんな所で使われているか、書く・読むことの利点と難しさ、どう学ぶとよいかが分かります。
今、アプリ全体をアセンブリで書くことはほとんどなく、「ごく一部を書く」「出力を読む」使い方が中心です。
OS の入口
Linux のソースの arch/x86/entry/entry_64.S は、冒頭に「system-call and fault low-level handling routines」とあり、syscall 命令の入口 entry_SYSCALL_64 がアセンブリで書かれている。
C では書けない部分
起動・組み込み
電源を入れた直後の初期化や、割り込みの入口、小さなマイコンの限られた処理。
メーカーの資料に沿って書く
性能の調査
コンパイラが何を出したかを確かめ、遅い原因(ループが最適化されていない等)を探す。
書くより読む
障害・解析
ソースの無いプログラムの不具合調査や、セキュリティの解析で逆アセンブル結果を読む。
gdb・objdump
| メリット | デメリット | |
|---|---|---|
| 速さ・大きさ | 不要な処理が入らない(Hello World の命令は33バイト) | 手書きが速いとは限らない(手元では GCC -O2 に負けた) |
| できること | システムコール・特別な命令・レジスタを直接扱える | あふれ・スタックの崩れを誰も止めてくれない |
| 持ち運び | CPU の動きがそのまま分かる | CPU・OS が変わると書き直し(x86-64 と ARM64 で番号もレジスタも違った) |
| 読みやすさ | 1行が1命令で、何をしているかははっきりしている | 1から100の和でも10行以上。大きなプログラムでは全体が見えにくい |
| 学ぶ価値 | メモリ・スタック・ポインタ・呼び出し規約が体で分かる | 覚える量が多く、最初の1本目の言語には向かない |
どう向き合うか(判断表)
| したいこと | まず使うもの |
|---|---|
| プログラムを速くしたい | アルゴリズムの見直しと -O2。アセンブリは gcc -S で確かめる側に使う |
| SIMD などの特別な命令を使いたい | コンパイラの組み込み関数(intrinsics)。全部を手で書かない |
| C のプログラムがどこで落ちたか知りたい | gdb の disassemble・stepi・info registers |
| OS の起動・割り込み・マイコンの初期化 | アセンブリ(CPU メーカーとボードの資料に沿って) |
| コンピューターの仕組みを学びたい | C を書いて gcc -S -O0 で読む → 小さな関数を NASM で書く |
| .NET の「アセンブリ」を調べている | 別物。.dll・.exe の単位の話で、アセンブリ言語は関係しない |
学ぶ順番
- 1C で小さな関数を書く足し算・ループ・配列。ポインタまで分かると読みやすい
- 2gcc -S -O0 で読むC の1行がどの命令になったかを対応づける
- 3gdb で stepi するレジスタ・フラグ・スタックの変化を見る
- 4NASM で Hello World と和の計算を書くシステムコール・ループ・call と ret
- 5-O2 の出力を読むコンパイラの工夫を知る
- 6別の CPU を見るARM64 や RISC-V を qemu で動かし、違いと共通点をつかむ
C のポインタとメモリの扱いは、関連記事「C言語とは|入門から環境構築・ポインタ・mallocまで実行例で解説」で扱っています。基本情報技術者試験では、以前は IPA が定めた学習用のアセンブラ言語 CASL II が選択できる言語の1つでした。IPA の「試験で使用する情報技術に関する用語・プログラム言語など」Ver.5.1(2023年11月6日改訂)では、基本情報技術者試験のプログラムは擬似言語の記述形式で示すとされ、CASL II の記載はありません。
- 1対象の CPU とビット数を確かめたx86-64 か ARM64 か。32ビットの資料を写していないか
- 2記法を確かめた% と $ があれば AT&T(右が入れ先)、無ければ Intel(左が入れ先)
- 3終わり方を書いた_start なら exit のシステムコール、関数なら ret
- 4呼び出し規約を守った引数は rdi・rsi…、戻り値は rax。rbx・rbp・r12〜r15 は保存する
- 5gdb で1命令ずつ確かめた思いどおりの値がレジスタに入っているかを見る
よくある質問
Q. アセンブリ言語とは何ですか?
A. CPU が実行する機械語の命令に、mov・add などの英字の名前を付け、人が読み書きできるようにした低水準言語です。機械語とほぼ1対1で対応し、CPU の種類ごとに命令が違います。
Q. アセンブラとアセンブリ言語の違いは何ですか? A. アセンブリ言語は言語、アセンブラはそれを機械語に変換するソフト(NASM・GNU as・MASM など)です。会話では言語のほうを「アセンブラ」と呼ぶこともあります。
Q. 機械語とアセンブリ言語の違いは何ですか?
A. 表しているものは同じで、機械語は数値(b8 01 00 00 00)、アセンブリ言語は文字(mov eax, 1)で書きます。objdump で機械語からアセンブリ言語に戻せます。
Q. C言語とアセンブリ言語の関係は?
A. C のコンパイラは、多くの場合アセンブリ言語を経由して機械語を作ります。gcc -S で、その途中のアセンブリ言語を見られます。
Q. アセンブリで書けば必ず速くなりますか? A. なりません。手元の実測では、手書きのアセンブリは GCC -O0 の約4倍速かったものの、GCC -O2 より遅く、ループを公式に置き換えた clang -O2 には大きく負けました。
Q. 初心者が最初に学ぶ CPU はどれがよいですか? A. 手元のパソコンで動かせるものが学びやすいです。Windows や多くの Linux のパソコンなら x86-64、Apple シリコンの Mac や Raspberry Pi なら ARM64 です。qemu-user を使えば、x86-64 の Linux でも ARM64 の機械語を動かせます。
Q. NASM と GNU as のどちらを使えばよいですか?
A. 自分で書くなら、Intel 記法でマニュアルの例と合わせやすい NASM が分かりやすいです。GCC の出力を読む・C と一緒にビルドするなら GNU as に慣れておくと便利です。GNU as でも .intel_syntax noprefix で Intel 記法を使えます。
Q. Windows でもこの記事のコードは動きますか? A. 動きません。システムコールの番号と呼び方、実行ファイルの形式(Windows は PE)が違うためです。この記事のコードは Linux の x86-64・ARM64 向けで、Windows では WSL などの Linux の環境で試すことになります。
Q. Segmentation fault が出ました。
A. まず exit(関数なら ret)を書いているかを確かめます。次に gdb で動かし、落ちた命令を x/3i $rip、レジスタを info registers で見ます。push と pop の数が合っているかも確かめます。
Q. 「アセンブリ」と .NET のアセンブリは同じものですか? A. 別物です。.NET のアセンブリは、.exe や .dll として作られる、展開やバージョン管理の単位です(Microsoft Learn)。
Q. アセンブリ言語は今も仕事で使われていますか? A. 全体を書くことは少なく、OS の入口や起動処理、組み込み機器、性能の調査、障害の解析で、書く・読む場面があります。Linux のカーネルのソースにも、システムコールの入口などアセンブリのファイルがあります。
まとめ
- アセンブリ言語は、機械語を
mov・addなどの文字で書いた低水準言語。アセンブラ(NASM・GNU as など)が機械語に変換し、objdump で逆に戻せる。 gcc -Sで C のアセンブリが見られる。-O0 は C に近く、-O2 では形が大きく変わり、clang -O2 はループを公式に置き換えていた。- Linux x86-64 の Hello World は、rax に番号、rdi・rsi・rdx に引数を入れて
syscall。strace に出たのは execve・write の2回で、命令は33バイトだった。 - gdb の
stepi・info registers・xで、レジスタ・フラグ・スタックの変化を1命令ずつ確かめられる。call は戻り先を積み、rsp が8バイト減った。 - ARM64 は番号もレジスタも違い(x8・x0〜、
svc #0)、命令は4バイトの固定長。手書きはコンパイラの -O2 に負けたので、アセンブリは「読んで確かめる」道具として使うのが近道。
参考・出典
確認日はいずれも 2026年10月29日です。
- Linux man-pages「syscall(2)」:https://man7.org/linux/man-pages/man2/syscall.2.html
- GNU Binutils「Using as」i386-Variations(AT&T Syntax versus Intel Syntax):https://sourceware.org/binutils/docs/as/i386_002dVariations.html
- NASM「The Netwide Assembler: NASM」Chapter 1 Introduction:https://www.nasm.us/doc/nasmdoc1.html
- GDB「Continuing and Stepping」:https://sourceware.org/gdb/current/onlinedocs/gdb.html/Continuing-and-Stepping.html
- Intel「Intel 64 and IA-32 Architectures Software Developer Manuals」:https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Arm「Procedure Call Standard for the Arm 64-bit Architecture(AAPCS64)」:https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
- x86-64 psABI(System V の x86-64 向け ABI の文書):https://gitlab.com/x86-psABIs/x86-64-ABI
- Linux カーネルのソース「arch/x86/entry/entry_64.S」:https://raw.githubusercontent.com/torvalds/linux/master/arch/x86/entry/entry_64.S
- Microsoft Learn「.NET のアセンブリ」:https://learn.microsoft.com/ja-jp/dotnet/standard/assembly/
- IPA「試験で使用する情報技術に関する用語・プログラム言語など」Ver.5.1:https://www.ipa.go.jp/shiken/syllabus/doe3um0000002djj-att/shiken_yougo_ver5_1.pdf
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「アセンブリ言語とは|機械語・アセンブラとの違いをNASMとgdbで実行」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。