見出し画像

【初心者向け】GASで始めるプログラミング最初歩

本記事は暇つぶし目的で書いたジョーク記事であること、ご了承ください。

※この記事では、Linux x86_64(WSL2やDocker環境含む)を前提に説明します。

ようやく来ました金曜日。

月曜日から始まる苦難の荒波を乗り越えるべく、八面六臂の大活躍、もしくは超絶的スルースキルを遺憾なく発揮してきたかたもいらっしゃるかも知れません。

不思議なことに、画面を見ても画面を見ても、一向に何も終わっていきません。

そんな、手を動かさなくても全て解決していく日々にあこがれを抱く中、そうではない事実に戦慄すら覚える日常に、ほんのちょっとのスパイスを。

少しだけ、私の暇つぶしにお付き合いいただければ幸いです!



「GAS」という言葉を聞いたこと、目にしたことはありますか?

noteでも多くの方が「GAS」を使って業務改善を行う、素晴らしいアイデアや仕組みの公開をされています。

是非 #GAS#GAS初心者 から、素敵な記事を探してみてください!

ところで……

「スプレッドシートの操作を自動化したい」

「Gmailと連動させてメールを自動送信したい」

そう思ってこの記事を開いてくださったみなさん……

残念でした。

今回扱うのは、GNU Assembler(GAS) です。


そして、安心してください!

Googleのアカウントも、Google Workspaceの契約も一切使いません。

必要なのは

Linux環境(ターミナル)と、CPUと聞いただけで白飯3杯いけるかどうか。

たったそれだけです。

それでは、めくるめくレジスタ操作の世界へ出発です!


そもそも GNU Assembler(GAS)とは?

GAS(GNU Assembler)は、GNUプロジェクトが開発しているアセンブラ(アセンブリ言語を機械語に変換するツール)です。

プログラミング関連の記事でよく目にするPythonやJavaScript、あるいはGoogle Apps Scriptなどは「高レベル言語」と呼ばれ、人間が読みやすいように作られています。


一方、アセンブリ言語は「CPUが直接理解する機械語に対応する、人間が読める形の低レベルな命令表現」です。

0か1だけで構成されているマシン語を、人間にわかりやすい表現を使って書いてある、そんな感じです。


前回の「入門」では、メモリ、スタック、関数呼び出し、そして printf を使って画面に文字を出してみました。


まさかの第3弾!

「超入門」から「入門」へ。

そしてここからが、「最初歩」です。


まだ一歩目じゃなかったのかよ。

そう思ったあなた。


私もそう思います。


Google Apps Scriptなら

function calc() {
    let a = 10;
    let b = 20;
    return a + b;
}

と書くだけで、関数専用の変数スペースを裏でスマートに区切ってくれます。

しかし、GNU Assemblerには「関数の中だけで使える安全な変数」なんてご都合主義的な概念は存在しません。

何ひとつ用意されていない荒野に、自分の手で変数の作業机を開拓していく必要があります。


環境構築(コマンド一発)

まさかの前回までに試してみてくださったかたは飛ばしてください。

環境構築も一瞬です。

お手元のLinux(WSL2やDocker環境でもOK)のディストリビューションに合わせて、以下のコマンドを実行するだけです。

  • Ubuntu / Debian / WSL:

sudo apt update && sudo apt install build-essential
  • Fedora / RHEL:

sudo dnf groupinstall "Development Tools"
  • Arch Linux:

sudo pacman -S base-devel

「Google Cloud Consoleを開いて、APIを有効化して、OAuth同意画面を設定して、認証キー(JSON)をダウンロードして……」なんて煩わしい設定は一切不要です。

コマンド一発。

最高ですね。



関数の中で変数を2個使いたい!

関数の中で生きている変数。

「ローカル変数」というやつですね。

関数の中で生まれて、関数が終わればさようなら。

そんな都合の良い、便利なやつです。


しかし、GNU Assemblerにはそんな親切な箱はありません。

前回、スタック(push と pop)を学びました。

今回のように、自分でローカル変数用のメモリ領域を確保するなら、その場所として使えるのがスタックです。


ただし、ここで問題が発生します。

スタックの現在位置を示す %rsp は、計算中に push したり別の関数を呼んだりするとコロコロ動いてしまいます

「変数 a の場所はどこだっけ?」と探そうにも、基準点が動き回っていたら迷子になってしまいますよね。


動かない基準点!ベースポインタ(%rbp)

そこで登場するのが

%rbp(Base Pointer)

です。


「%rsp(スタックポインタ)が動き回るなら、関数に入った瞬間のスタック位置を %rbp にメモして固定しておけばいいじゃない」

という作戦です。


この「関数の作業スペース(領地)」のことを スタックフレーム と呼びます。


お決まりの儀式がある!スタックフレームを作る!

コンパイラが吐き出すアセンブラを見ると、関数の先頭と末尾にお決まりの儀式が登場することがあります。


【関数の先頭(プロローグ)】

push %rbp          # 1. 呼び出し元の古い %rbp をスタックに保存
mov %rsp, %rbp     # 2. 現在のスタック位置を、新しい基準点(%rbp)にする
sub $16, %rsp      # 3. スタック領域を16バイト確保!


【関数の末尾(エピローグ)】

mov %rbp, %rsp     # 1. スタック位置を基準点に戻す(変数の部屋を片付ける)
pop %rbp           # 2. 古い %rbp を復元
ret                # 3. 呼び出し元へ戻る


イメージ図

高いアドレス
    │
    ├───────────────┤
    │ 戻り先アドレス │ (call で自動プッシュされる)
    ├───────────────┤
    │ 古い %rbp     │ ← ここが新しい %rbp(基準点!)
    ├───────────────┤
    │  変数 a (10)  │  -4(%rbp)
    ├───────────────┤
    │  変数 b (20)  │  -8(%rbp)
    ├───────────────┤
    │  (余った領域)│  -12(%rbp) 〜 -16(%rbp)
    ├───────────────┤ ← %rsp(ここまで広げた)
    ↓
低いアドレス

※x86_64ではスタックは「低いアドレス」に向かって伸びるので、変数は基準点からマイナスした位置になります。



変数の正体:今回のコードでは -4(%rbp) という現実

高級言語では a や b という可愛い名前がついていますが、CPUの世界には名前のついた部屋なんてありません。

あるのはスタックの領地争いだけです。


今回のコードでは、


  • 変数 a を -4(%rbp)(基準点から4バイト下)

  • 変数 b を -8(%rbp)(基準点から8バイト下)

という無機質なアドレス(番地)に手動で割り当てています。



実際に動かしてみると?

では、ローカル変数 a = 10 と b = 20 を作って足し算し、printf で表示するプログラムを書いてみます。


※ちなみに今回のコードはコンパイラに最適化させて生成したものではなく、あえて人間がスタックフレームを手で組み立てています


local_var.s という名前で保存します。

.global main

.section .rodata
msg:
    .string "Result: %d\n"

.text
main:
    # --- プロローグ(スタックフレーム作成) ---
    push %rbp
    mov %rsp, %rbp
    sub $16, %rsp          # スタック領域を16バイト確保

    # --- ローカル変数の代入 ---
    # let a = 10;
    movl $10, -4(%rbp)

    # let b = 20;
    movl $20, -8(%rbp)

    # --- 計算: a + b ---
    movl -4(%rbp), %eax    # a の値を %eax に読み込む
    addl -8(%rbp), %eax    # b の値を %eax に足す(結果: 30)

    # --- printf を呼び出す ---
    leaq msg(%rip), %rdi   # 第1引数: フォーマット文字列
    movl %eax, %esi        # 第2引数: 計算結果(30)
    xor %eax, %eax         # 浮動小数点引数は0個
    call printf

    # --- エピローグ(スタックフレーム解体) ---
    mov $0, %eax           # return 0;
    leave                  # mov %rbp, %rsp と pop %rbp を一発でやる便利命令
    ret


ポイントはいったいどこなんだ!?

  • movl や addl

末尾の l(ロング)は、「4バイト(32bit整数)」を扱う指示です。


  • leave 命令

mov %rbp, %rsp と pop %rbp をまとめてやってくれるCPUの親切機能です。


  • スタックの調整とアライメント

この関数では、次の call printf を実行する直前に %rsp が16バイト境界に揃うよう、スタック領域を16バイト確保(sub $16, %rsp)して調整しています。


  • 余った8バイトの謎

変数 a(4バイト)と b(4バイト)で、合計8バイトしか使っていません。
「じゃあ残りの8バイトは何?」と思いますよね。

今回は使いません。せっかく16バイト借りたのに、8バイト余りました。

アライメントというお作法は、こういう「なんか余った土地」を平然と生み出します。

CPUとは不動産管理に厳しいルールをお持ちなやつなんです。



コンパイルと実行

gcc でリンクして実行してみます。

gcc -no-pie local_var.s -o local_var
./local_var

その結果

Result: 30

と表示されれば大成功です!



普段のコードの裏で起きていること

Google Apps Scriptで

function add() {
    let a = 10;
    let b = 20;
    return a + b;
}

と書いた時、裏側では

  1. 呼び出し元のベースポインタを退避する

  2. 新しい基準点をセットする

  3. スタックポインタを引き下げて部屋を借りる

  4. 基準点からのマイナスオフセットに変数の値を置く

  5. 計算が終わったら部屋を明け渡してベースポインタを元に戻す

という、気の遠くなるような引越し&撤収作業が行われています。

現代の賢いコンパイラなら、最適化によってそもそもスタックを使わずレジスタだけで爆速処理してしまうこともありますが、基本構造としてはこういうことです。


今回のようにスタック上へ置いたローカル変数なら、「スコープを抜けると消える」というのは、魔法で消滅しているのではなく、単にスタックポインタを元に戻して部屋を明け渡しているだけだったのです。


GASをマスターして業務効率化(?)を目指そう!

今回、最初歩として

  • ローカル変数用の領域確保

  • ベースポインタ(%rbp)

  • スタックフレームの作成(プロローグ)と解体(エピローグ)

  • -4(%rbp) というオフセット指定

  • leave 命令

を見てきました。


GASをどこまで極めても、スプレッドシートの自動化やGmail連携は相変わらず1ミリもできません。


ですが、高級言語で let a = 10; と書くたびに、裏でどれだけ泥臭い部屋の領地争いが起きているのか、その片鱗を味わっていただけたのではないでしょうか。


高級言語さん、今日もお部屋の片付け、ありがとう!


それでは、良きGAS(GNU Assembler)ライフを!



📖 エッセイ

どうやら私は、ひとりだけ別の世界に住んでいるようだ (全4話)


📖 短編小説


PR記事を書かせて頂いています!


メンバーシップ始めました!
まだ手探りですが、ご要望/無茶振り/質問、可能な限りお受けします!


何でもどうぞ!答えられるものは全て答えます!

設置はお済みですか?図らずも人の庭でnote質問箱を試す!|へろん


有料ツールに挑戦中!

OBS Studio用リモコン | OBS画面を前面に出さずに操作できます!


サイトマップあります!

いいなと思ったら応援しよう!

へろん いつも応援ありがとうございます! 快適な制作を進めるためのおやつ活動費に使わせて頂きます!