Practice5 Explanation - GawinGowin/libasm GitHub Wiki
-
メモリアドレッシングの習得
- ポインタ操作の実践
- メモリアクセスパターンの理解
- 効率的なメモリ読み取り技術
-
ループ構造の実装
- 条件付きループの設計
- インデックス管理
- 終了条件の適切な設定
-
標準ライブラリ関数の理解
- C標準ライブラリの内部動作
- 文字列処理の基礎
- 実用的な関数設計パターン
; practice5.s - strlen 実装
section .text
global my_strlen
my_strlen:
; 引数: rdi (文字列ポインタ)
; 戻り値: rax (長さ)
mov rax, 0
.loop:
cmp byte [rdi + rax], 0
je .done
inc rax
jmp .loop
.done:
ret
size_t strlen(const char *s);
機能:
- NULL終端文字列の長さを計算
- NULL文字('\0')は長さに含まない
- 戻り値: 文字数(size_t型 = unsigned long)
文字列 "Hello"のメモリ配置:
アドレス: 0x1000 0x1001 0x1002 0x1003 0x1004 0x1005
内容: 'H' 'e' 'l' 'l' 'o' '\0'
インデックス: 0 1 2 3 4 5
mov rax, 0 ; カウンタを0で初期化
設計判断:
-
rax
をカウンタとして使用 - 戻り値レジスタと兼用で効率化
- 初期値0から開始
.loop:
cmp byte [rdi + rax], 0 ; [文字列先頭 + オフセット] と 0 を比較
je .done ; NULL文字なら終了
inc rax ; カウンタをインクリメント
jmp .loop ; ループ継続
重要なポイント:
[rdi + rax] ; ベースレジスタ + インデックスレジスタ
-
rdi
: 文字列の開始アドレス(ベース) -
rax
: 現在の文字位置(インデックス) - 組み合わせで現在文字のアドレスを計算
cmp byte [rdi + rax], 0
-
byte
: 1バイト(8bit)単位でアクセス - 文字は通常1バイトで格納
- 64bit全体ではなく必要な部分のみアクセス
byte
はメモリオペランドサイズ指定子です。
cmp byte [rdi + rax], 0
このbyte
は:
- メモリから1バイト(8bit)を読み取ることを指定
-
[rdi + rax]
のメモリアドレスから1バイト分のデータを取得 - 文字列の1文字(char型)を読み取る
cmp byte [rdi + rax], 0 ; 1バイト読み取り
cmp word [rdi + rax], 0 ; 2バイト読み取り
cmp dword [rdi + rax], 0 ; 4バイト読み取り
cmp qword [rdi + rax], 0 ; 8バイト読み取り
文字列の場合:
- C言語の
char
は1バイト - 文字列は1バイトずつ格納されている
- NULL終端文字
\0
も1バイト
もしbyte
を省略すると:
cmp [rdi + rax], 0 ; サイズ不明でアセンブラエラー
正しい指定:
cmp byte [rdi + rax], 0 ; 1バイト比較 = 1文字ずつチェック
文字列 "Hello" のメモリ:
アドレス: 0x1000 0x1001 0x1002 0x1003 0x1004 0x1005
内容: 'H' 'e' 'l' 'l' 'o' '\0'
↑
byte [rdi + rax] = 'H' (1バイト = 0x48)
重要なポイント:
-
byte
は変数型ではなく、メモリアクセスのサイズを指定する修飾子 - アセンブリでは明示的にサイズを指定する必要がある
- 文字列処理では
byte
指定が必須
初期状態:
- rdi = 0x1000 (文字列先頭アドレス)
- rax = 0
- メモリ: [0x1000]='A', [0x1001]='B', [0x1002]='C', [0x1003]='\0'
1回目ループ:
- [rdi + rax] = [0x1000 + 0] = 'A' (0x41)
- cmp byte [0x1000], 0 → 'A' ≠ 0
- inc rax → rax = 1
2回目ループ:
- [rdi + rax] = [0x1000 + 1] = 'B' (0x42)
- cmp byte [0x1001], 0 → 'B' ≠ 0
- inc rax → rax = 2
3回目ループ:
- [rdi + rax] = [0x1000 + 2] = 'C' (0x43)
- cmp byte [0x1002], 0 → 'C' ≠ 0
- inc rax → rax = 3
4回目ループ:
- [rdi + rax] = [0x1000 + 3] = '\0' (0x00)
- cmp byte [0x1003], 0 → '\0' = 0
- je .done → ループ終了
結果: rax = 3 (文字列長)
-
メモリ操作の基礎
- ポインタ演算の実装
- インデックスアクセスパターン
- バイト単位メモリ操作
-
ループアルゴリズム
- 線形探索の実装
- 条件判定とループ制御
- 効率的な反復処理
-
文字列処理の基礎
- NULL終端文字列の理解
- 文字エンコーディングの考慮
- 標準ライブラリ互換性
O(n) - n は文字列長
- 各文字を1回ずつ検査
- 線形時間で完了
O(1) - 定数時間
- 追加メモリ使用なし
- レジスタのみ使用
1文字あたりの処理:
- cmp byte [rdi + rax], 0 ; 2-3サイクル(メモリアクセス)
- je .done ; 1サイクル(予測成功時)
- inc rax ; 1サイクル
- jmp .loop ; 1サイクル
1文字あたり約5-6サイクル
文字列長nに対して約5n-6nサイクル
my_strlen_optimized:
mov rax, 0
; 8バイト境界までの調整
.align_loop:
test rdi, 7 ; 8バイト境界チェック
jz .word_loop ; 境界なら8バイト処理へ
cmp byte [rdi + rax], 0 ; 1バイトずつ処理
je .done
inc rax
jmp .align_loop
.word_loop:
mov rdx, [rdi + rax] ; 8バイト一括読み込み
; マジックナンバーでNULL検出
; (実装詳細は高度なため省略)
.done:
ret
my_strlen_simd:
; SSE2/AVX命令を使用した高速実装
; 16バイト/32バイト単位での並列処理
; (実装詳細は発展課題として)
; 間違い: バイト指定なし
cmp [rdi + rax], 0 ; 8バイト比較してしまう
問題点:
- 意図しない8バイト読み込み
- 隣接メモリの値も影響
- 不正な結果
; 間違い: カウンタ更新忘れ
.loop:
cmp byte [rdi + rax], 0
je .done
; inc rax が抜けている!
jmp .loop
; 間違い: ポインタ自体を変更
.loop:
cmp byte [rdi], 0
je .done
inc rdi ; ポインタを変更(危険)
inc rax
jmp .loop
問題点:
- 元のポインタ値を破壊
- 呼び出し元に影響
- デバッグ困難
; strchr - 文字検索
; strcmp - 文字列比較
; strncmp - 長さ制限比較
; strcpy - 文字列コピー
; UTF-8文字列の長さ計算
; マルチバイト文字の考慮
; 文字境界の正確な検出
; NULLポインタチェック
; メモリ境界の検証
; セグメンテーションフォルト回避
// main.c
extern size_t my_strlen(const char *s);
#include <stdio.h>
#include <string.h>
int main() {
const char *test = "Hello, Assembly!";
size_t my_len = my_strlen(test);
size_t std_len = strlen(test);
printf("my_strlen: %zu\n", my_len); // 16
printf("strlen: %zu\n", std_len); // 16
printf("Match: %s\n", my_len == std_len ? "Yes" : "No");
return 0;
}
nasm -f elf64 practice5.s -o practice5.o
gcc -c main.c -o main.o
gcc practice5.o main.o -o test
./test
この練習により、メモリ操作と文字列処理の基礎が身につき、実用的なライブラリ関数の実装能力が向上します。