Objective-Cにおいて [nil intValue] がエラー無く0を返す理由

iOSの開発がSwiftで盛んに行われていますが、なんだかんだ、Objctive-Cもまだまだ現役だったりする。
言語仕様を完全に理解しているわけではないので、その都度、疑問がでてきたりする。
この記事では、**nilに対するメソッド呼び出しでエラーが発生しない** 点の調査に取り組んでみようと思う。

# ことの発端
とあるところで、「キーが無いのに `NO` が返ってくるが誤動作は起きないのか？」という質問を得た。
簡単なプログラムにすると以下の通りになる。

```objc:test.m_(問題のプログラム)
#import <Foundation/Foundation.h>

int main() {
    NSMutableDictionary *mdic = [NSMutableDictionary dictionary];
    [mdic setObject:@"12345678" forKey:@"id"];
    
    BOOL a = [[mdic objectForKey:@"flag"] boolValue];
    if(a == NO)
        NSLog(@"a is NO\n");
    else
        NSLog(@"a is not NO\n");
    return 0;
}
```
実際にコンパイルして実行してみるとわかるが、`segmentation fault:11` とか `Abort trap: 6` が発生することなく、`NO` が返ってくる。

```bash:なぜかエラーなくNOが返ってくる
% cc test.m -framework Foundation
% ./a.out                         
2021-08-XX XX:53:34.840 a.out[4547:146043] a is NO
```
この挙動は不思議だ。不思議な点は **「返り値を得られること」ではない**。 `[mdic objectForKey:@"flag"]`は`nil`で **「nilに対してメソッドを実行したのにエラーが発生しない」** というのが気になってしまった。

# 他の言語の場合
他のオブジェクト指向言語の場合を見てみると、Objctive-Cの挙動が不思議に思えてくる。

## Javaの場合
「nilに対してメソッドを実行する」上のプログラムをJavaで書き直すと次のようになる。もう動かさなくてもわかる。どう考えても `NullPointerException` が発生することは間違い無い。

```java:Test.java
public class Test {

    public class NSString {
        public boolean boolValue() {
            return true;
        }
    }

    public static void main(String[] args) {
        NSString str = null;
        System.out.println(str.boolValue());
    }
}
```

```bash:NullPointerExceptionが発生
% java Test.java
Exception in thread "main" java.lang.NullPointerException
        at Test.main(Test.java:11)
```
動かす前からわかっていたが、**NullPointerExceptionが発生した。Javaではnullに対してメソッドを呼び出してはいけないようだ。** ← 当たり前です

## C++の場合
「nilに対してメソッドを実行する」上のプログラムをC++で書き直すと次のようになる。これも動かさなくてもわかる。`nullptr `をデリファレンスして、boolValue関数を呼び出しているのだから。

```c++:test.cpp
#include <iostream>

class NSString {
   public:
    NSString() { a = false; }
    ~NSString() {}
    bool boolValue();

   private:
    bool a;
};

bool NSString::boolValue() { return a; }

int main() {
    NSString *a = nullptr;
    a->boolValue();
    return 0;
}
```

```bash:
% g++ -O0 test.cpp
% ./a.out
zsh: segmentation fault  ./a.out
```
予想通り、`segmentation fault` でプログラムからエラーが発生した。

※ 記事の都合的に、`a->boolValue();` の時点でエラーが発生したみたいになっているが、実際のアセンブリをlldbで追うと、この時点ではセグメンテーション違反になっていない。実は、boolValue関数の中で自身のメンバ変数aを読み出すときに、セグメンテーション違反が発生している。`NSString *a`のポインタを元に、自身のメンバ変数を読み出しているので、結局アドレスの0番地付近を読み出すことになり、セグメンテーション違反が発生したわけであるが...

## C言語の場合
オブジェクト指向言語ではないがC言語でも試してみよう。これもNULLポインタのデリファレンスをしているので、エラー発生が目に見える。

```c:test.c
#include <stdbool.h>
#include <stdio.h>

typedef struct {
    bool a;
} NSString;

bool NSString_boolValue(NSString* self) { return self->a; }

int main() {
    NSString* a = NULL;
    NSString_boolValue(a);
    return 0;
}
```

```bash:
% cc -O0 test.c 
% ./a.out
zsh: segmentation fault  ./a.out
```
やっぱりダメ。やはり呼び出す前に`nullptr`や`NULL`でないことを確かめないと良くない...

**「ヌルに対してメソッドを呼び出す、ヌルに対して変数を参照するなんてことは、おこがましいこと」** だと、コンピュータに返されてしまった。他の言語と比べると、nilに対してメソッドを呼び出しているのにエラーを出さない、Objective-Cの挙動は **直感に反している** と感じる。

# The behavior is by design
> これは仕様です

[Appleの公式ドキュメント](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html)から引用すると以下の通りです。

> **Sending Messages to nil**
In Objective-C, it is valid to send a message to `nil`—it simply has no effect at runtime. There are several patterns in Cocoa that take advantage of this fact. The value returned from a message to `nil` may also be valid:

> - If the method returns an object, then a message sent to `nil` returns `0 (nil)`. For example:
`Person *motherInLaw = [[aPerson spouse] mother];`
- If the `spouse` object here is `nil`, then mother is sent to nil and the method returns `nil`.
- If the method returns any pointer type, any integer scalar of size less than or equal to `sizeof(void*)`, a `float`, a `double`, a `long double`, or a `long long`, then a message sent to `nil` returns `0`.
- If the method returns a `struct`, as defined by the [OS X ABI Function Call Guide](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html#//apple_ref/doc/uid/TP40002521) to be returned in registers, then a message sent to `nil` returns `0.0` for every field in the `struct`. Other `struct` data types will not be filled with zeros.
- If the method returns anything other than the aforementioned value types, the return value of a message sent to `nil` is undefined.

※ `call function` とかじゃなくて、`send messages` とありますが、これはあとで説明を入れます。

[*Objective-C的 null(nil)安全*](https://yohhoy.hatenadiary.jp/entry/20200501/p1) の日本語での解釈がわかりやすかったので、言葉を借りて説明すると。
> Objective-Cにおいてメッセージ送信先のレシーバがnilの場合 *(nilに対してメソッドを呼び出しても)*、Objective-Cランタイムはなにもしない（エラーは発生せずメソッドも呼び出されない）。メソッドが戻り値型を持つ場合、値 0 相当が返却されたかのように振る舞う。 *※斜体以外引用*

どうりでエラーも返さないわけだし`BOOL型`だと`NO`が返ってくるわけだ。そういう仕様と言われても納得できるような気にするが、**じゃあどこで一体 `nil` をチェックして0を返すというのか？** ならば、その仕様がどうやって実装されているかみてみよう！

※ ちなみに、上のドキュメントは `Retired Document` となっている。[新しいObjective-Cのドキュメント](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html#//apple_ref/doc/uid/TP40011210-CH4-SW22) だと
>  If you do send a message to `nil`, obviously nothing happens.
> Note: If you expect a return value from a message sent to `nil`, the return value will be `nil` for object return types, `0` for numeric types, and `NO` for `BOOL` types. Returned structures have all members initialized to zero.

となっておりとても簡素ですね...

# 実装を探る
さてこれから実装を探る。どうするかというと、アセンブリをデバッガを使って1つ1つ命令を追っていけば良い。アセンブリを追うために以下のプログラムを用意した。

- `intValue`は`NSString`のメソッドなので`Foundation/NSString.h`をインポート
- lldbでディスアセンブルのときにシンボルを指定するために、`objctest`関数に切り分け

```objc:test2.m
#import<Foundation/NSString.h>

int objctest() {
    id a = nil;
    int b = [a intValue];
    return 0;
}

int main() {   
    return objctest();
}
```

`-O0`で最適化なし、`-g`でデバッグ情報を埋め込みのオプションをつけて、コンパイル。そして実行バイナリを指定してlldbを起動

```bash:
% cc -O0 -g test2.m -framework Foundation
% lldb a.out                             
(lldb) target create "a.out"
Current executable set to '/absolute/path/a.out' (x86_64).
(lldb) 
```

### (補足) lldbの使い方
**lldbはデバッガ**、以下のコマンドで基本的な操作ができる

|コマンド|説明|備考|
|:-:|:--|:--|
|b / br|ブレイクポイントを設定|`b main`: main関数にブレイクポイントをセットする|
|r / run|起動時に指定したファイルを実行||
|n / next|次の行までステップ実行||step over(関数呼び出しは実行)|
|ni / nexti|次の機械語一つを実行|step over(関数呼び出しは実行)|
|c / continue|breakを終了してプログラムを再開||
|s / step|次の行までステップ実行|step into(関数呼び出しの中へ)|
|si / stepi|次の機械語一つを実行|step into(関数呼び出しの中へ)|
|po|変数の中身を表示|`po a` : 変数aの中身を表示|
|x|指定したアドレスの中身を表示||
|di / disassemble|逆アセンブルを表示|`di -m -n main` main関数をディスアセンブル |
|register read|レジスタを表示||

`objctest`関数にブレイクポイントをセットし、`r`コマンドで、ブレイクポイントまでプログラムを実行する。

```bash:
(lldb) b objctest
Breakpoint 1: where = a.out`objctest + 8 at test1.m:4:8, address = 0x0000000100003f68
(lldb) r
Process 14034 launched: '/absolute/path/a.out' (x86_64)
Process 14034 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003f68 a.out`objctest at test1.m:4:8
   1    #import<Foundation/NSString.h>
   2   
   3    int objctest() {
-> 4        id a = nil;
   5        int b = [a intValue];
   6        return 0;
   7    }
Target 0: (a.out) stopped.
(lldb) 
```
するとしっかり止まってくれるので、`objctest`関数をディスアセンブルしてみる。

```bash:
(lldb) di -m -n objctest

** 3    int objctest() {

a.out`objctest:
    0x100003f60 <+0>:  pushq  %rbp
    0x100003f61 <+1>:  movq   %rsp, %rbp
    0x100003f64 <+4>:  subq   $0x10, %rsp

-> 4        id a = nil;

->  0x100003f68 <+8>:  movq   $0x0, -0x8(%rbp)

** 5        int b = [a intValue];

    0x100003f70 <+16>: movq   -0x8(%rbp), %rdi
    0x100003f74 <+20>: movq   0x4085(%rip), %rsi        ; "intValue"
    0x100003f7b <+27>: callq  *0x7f(%rip)               ; (void *)0x00007fff6ac81800: objc_msgSend
    0x100003f81 <+33>: xorl   %ecx, %ecx
    0x100003f83 <+35>: movl   %eax, -0xc(%rbp)

** 6        return 0;
   7    }
   8   

    0x100003f86 <+38>: movl   %ecx, %eax
    0x100003f88 <+40>: addq   $0x10, %rsp
    0x100003f8c <+44>: popq   %rbp
    0x100003f8d <+45>: retq   

(lldb) 
```
`disassemble`コマンドを使うと、lldbは`AT&T`記法(srcが左、distが右)のアセンブリを返してくれる。
※ 実行環境のアーキテクチャは`x86_64` 

さてここから、実際に動いているアセンブリを読んで紐解いていく。

```nasm:
movq   $0x0, -0x8(%rbp)
```
`mov`は代入命令、左の値を右の場所へ代入する。`q`はサフィックスで64bitを表すので、`movq`は64bitの代入命令。`nil`は定義上`(void *)0`なので、数値では`0`で表せる、 `$0x0` は `0`を表す。つまりこれは`nil`。`-0x8(%rbp)` は `rbp`レジスタの値から`0x8`減算した場所示すもので、変数aのアドレスである(`&a`の値と`-0x8(%rbp)`は同じ)。つまり、この行では、変数aに`nil`を代入していることがわかる。

```nasm:
movq   -0x8(%rbp), %rdi
```
`-0x8(%rbp)` は変数aのアドレスであるので、この行では、変数aの値を`%rdi`レジスタへ代入している。変数aには、`nil,0`が入っているので、`%rdi`レジスタの値はもちろろん`0`である。

```nasm:
movq   0x4085(%rip), %rsi        ; "intValue"
```
`0x4085(%rip)` は`rip`レジスタ(プログラムカウンタ)の値から`0x4085`加算した場所示すもので、セレクタ(Objective-Cにおけるメソッド名)`intValue`があるポインタ。セレクタは`SEL`型で実体は`const char *`なので`0x4085(%rip)` 値たどると`intValue`という文字列があるだけである。

プログラムカウンタは次の命令のアドレスを指すので、`%rip`レジスタの値はこの場合、`0x100003f7b`になる。

```bash:
    0x100003f74 <+20>: movq   0x4085(%rip), %rsi        ; "intValue"
->  0x100003f7b <+27>: callq  *0x7f(%rip)               ; (void *)0x00007fff6ac81800: objc_msgSend
```
`0x100003f7b`に`0x4085`加算した場所`0x100008000`を見ると、アドレスが格納されている。このアドレスは`intValue`という文字列を入れているアドレスなので、`%rsi`レジスタには、 この場合`0x00007fff761bb3e2`、`"intValue"`の文字列を指すポインタが入ることになる。

```bash:
(lldb) x/1a 0x100008000
0x100008000: 0x00007fff761bb3e2 
(lldb) x/1s 0x00007fff761bb3e2
0x7fff761bb3e2: "intValue"
(lldb) 
```

```nasm:
callq  *0x7f(%rip)               ; (void *)0x00007fff6ac81800: objc_msgSend
```
`call`は指定したアドレスの関数を呼び出します。`q`は言わずもがな64bitの意味です(命令の接尾にqがついていたら64bitでのものです)。
ここでは、Objective-Cのソースでどうやら呼び出した覚えのない関数`objc_msgSend`の呼び出しが行われている。

ここの命令が、`%rdi`レジスタと`%rsi`レジスタに値を入れて`callq`ということは64bitの整数を2つ引数をもつ関数の呼び出しであることは、[Appleのx86_64の呼び出し規則 (Application Binary Interface)](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/LowLevelABI/140-x86-64_Function_Calling_Conventions/x86_64.html)を紐解くとわかる。ページに飛ぶと、*"System V Application Binary Interface AMD64 Architecture Processor Supplement"* に同じと書かれている。なので、そのタイトルの文書の`3.2.3 Parameter Passing`を参照すると、関数の呼び出しと引数の渡し方について以下のように書いてある。

> If the class is INTEGER, the next available register of the sequence %rdi,
%rsi, %rdx, %rcx, %r8 and %r9

`objc_msgSend`関数の第一引数の値が`%rdi`レジスタ、第2引数の値が`%rsi`レジスタに入るわけである。つまり
`[]`は、C言語における`objc_msgSend` 関数の呼び出しと同じ形に書き換えることができるわけだ。

```objc
int b = [a intValue];
// 同じ
int b = objc_msgSend(a , "intValue");
```

だから、`intValue`メソッドは、a.outのバイナリの中で`id`型変数aの値を元に場所を解決しているのではなく、関数`objc_msgSend`の中で場所を解決しているわけである。
以下みたいにいポインタを元にメソッドを呼び出しているわけではない、良かった。

```c
int b = a->intValue();
```

結局のところ、「**`objc_msgSend`関数内でnilチェックが行われて、0が関数の返り値として戻ってくるわけなのか？**」それを確認するためにも、`stepi`コマンドを使って`objc_msgSend`関数の中へ入ってみる。

```bash:
(lldb) stepi
Process 14104 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x00007fff6ac81800 libobjc.A.dylib`objc_msgSend
libobjc.A.dylib`objc_msgSend:
->  0x7fff6ac81800 <+0>:  testq  %rdi, %rdi
    0x7fff6ac81803 <+3>:  je     0x7fff6ac81878            ; <+120>
    0x7fff6ac81806 <+6>:  testb  $0x1, %dil
    0x7fff6ac8180a <+10>: jne    0x7fff6ac81883            ; <+131>
Target 0: (a.out) stopped.
(lldb) 
```
`objc_msgSend`関数のアセンブリも一行づつ追っていこう。

```nasm:
testq  %rdi, %rdi
```
`test`は、左と右の値の論理積を計算し、結果が`0`ならゼロフラグ(ZF)を立てる命令。`%rdi`レジスタには、変数aに入っていた`nil`つまり`0`が入っているので、`0 & 0` の論理積はもちろん`0` になる。
結果が`0`なのでゼロフラグ(ZF)が立つ。これは`0`であることのチェックなので、**`nil`チェックが`objc_msgSend`関数に入ってすぐに行われている。**

```nasm:
je     0x7fff6ac81878
```
`je` はゼロフラグ(ZF)が立っている場合、指定したアドレスへジャンプする命令。先程の論理積の結果からゼロフラグが立っているので、この場合は`0x7fff6ac81878`へジャンプする。

https://qiita.com/DQNEO/items/76a99445e3adde72eb2d

`stepi`コマンドで進めると`0x7fff6ac81878`へジャンプする。

```bash:
(lldb) stepi
Process 14104 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x00007fff6ac81878 libobjc.A.dylib`objc_msgSend + 120
libobjc.A.dylib`objc_msgSend:
->  0x7fff6ac81878 <+120>: xorl   %eax, %eax
    0x7fff6ac8187a <+122>: xorl   %edx, %edx
    0x7fff6ac8187c <+124>: xorps  %xmm0, %xmm0
    0x7fff6ac8187f <+127>: xorps  %xmm1, %xmm1
Target 0: (a.out) stopped.
(lldb) 
```
このあたりのアセンブリをもうちょっと見てみる。すると、`0x7fff6ac81882`では`retq`命令が呼ばれており、ここで、`objc_msgSend`関数からもとのプログラムへ戻る。

```bash:
(lldb) x/8i 0x7fff6ac81878
->  0x7fff6ac81878: 31 c0                 xorl   %eax, %eax
    0x7fff6ac8187a: 31 d2                 xorl   %edx, %edx
    0x7fff6ac8187c: 0f 57 c0              xorps  %xmm0, %xmm0
    0x7fff6ac8187f: 0f 57 c9              xorps  %xmm1, %xmm1
    0x7fff6ac81882: c3                    retq   
    0x7fff6ac81883: 41 89 fb              movl   %edi, %r11d
    0x7fff6ac81886: 41 83 e3 0f           andl   $0xf, %r11d
    0x7fff6ac8188a: 4c 8d 15 ff f9 71 27  leaq   0x2771f9ff(%rip), %r10    ; objc_debug_taggedpointer_classes
(lldb) 
```
`objc_msgSend`関数の挙動は、第一引数が`nil`だったら`0`を返すわけなので、おそらくここはレジスタを`0`に埋める命令が並んでいるの違いない。
*"System V Application Binary Interface AMD64 Architecture Processor Supplement:*の`3.2.3 Parameter Passing Returning of Values`を参照すると、

> 2.If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first argument.
On return %rax will contain the address that has been passed in by the caller in %rdi.
3.If the class is INTEGER, the next available register of the sequence %rax, %rdx is used.
4.If the class is SSE, the next available SSE register of the sequence %xmm0, %xmm1 is used.

返り値にどのレジスタを使うかの決定順序と、
> - %rax temporary register; with variable arguments passes information about the number of SSE registers used; 1st return register
- %rdx used to pass 3rd argument to functions; 2nd return register
- %xmm0–%xmm1 used to pass and return floating point arguments

レジスタの利用目的を見ることができる。つまり整数`0`を返すなら、次のアセンブリを期待する。

```nasm:
movq   $0x0, %rax
movq   $0x0, %rdx
```
しかし、実際は以下のアセンブリが実行される。

```nasm:
xorl   %eax, %eax
xorl   %edx, %edx
```

`xor`は右と左の値の排他的論理和を取り結果を右?へ格納する。右と左の値が同じ場合は`0`が入るため、`xorl   %eax, %eax`と書くと`%eax`に0を代入することと同値である。`l`は32bitを表す。`%eax`は`%rax`の下位32bitの部分を使っているで、返り値に`int`型(32bit)を期待しているの場合は、呼び出し元で`%eax`から値を受け取れるので問題が無いように見える。しかし、返り値に`long long`型(64bit)を期待している場合はどうだろうか。`xorl   %eax, %eax`だと上位32bitは`0`であることが保証されないように感じる(`xorq   %rax, %rax`こうじゃないの？)。しかし、この書き方で問題は無い。
[*"Intel® 64 and IA-32 Architectures Software Developer’s Manual"*](https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.htm) の`3.4.1.1 General-Purpose Registers in 64-Bit Mode`を参照すると、

> When in 64-bit mode, operand size determines the number of valid bits in the destination general-purpose register:
- 64-bit operands generate a 64-bit result in the destination general-purpose register.
- 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register.
- 8-bit and 16-bit operands generate an 8-bit or 16-bit result. The upper 56 bits or 48 bits (respectively) of the destination general-purpose register are not modified by the operation. If the result of an 8-bit or 16-bit operation is intended for 64-bit address calculation, explicitly sign-extend the register to the full 64-bits.

とある。ここでは、32bitのレジスタに対して操作すると上位32bitが`0`で埋められると書かれている。つまり`xorl   %eax, %eax`だけで、`%rax`レジスタの全てのビットを`0`にできていたのである。これで、`objc_msgSend`関数の返り値として、64bitまでの大きさの整数を`0`で返すことができる。
それならば、%edxを`0`で初期化する意味はなんだろうか？`%edx`は`%rdx`の下位32bitの部分を使っている。`%rdx`のレジスタの目的は*2nd return register*とされているが、C言語での関数の返り値は1つのみである。じゃあ、一体何に使っているかというと、`__int128`型を返すときの上位64bitとして`%rdx`を使っている。そのため、`__int128`型で`0`を返すためには、上位64bitの`%rdx`も`0`にしておかないとならない。

ここまで整数の話をしたのだから、残り2つの命令は浮動小数点に関するものである。

```nasm:
xorps  %xmm0, %xmm0
xorps  %xmm1, %xmm1
```
ここでも`xor`命令を使う。[`0`初期化に`xor`を使うのは命令のバイトする減らすため](https://www.mztn.org/lxasm64/amd08_calc.html)である。`ps`は`packed single`で、[128bitある`%xmm0`レジスタを32bitずつに区切って演算します](https://warabanshi.hatenablog.com/entry/2014/05/21/001419)。

https://www.officedaytime.com/tips/simd.html

浮動小数点の場合は`%xmm0-%xmm1`を使って返り値を渡すが `float`型は32bit、`double`型は64bitなので、`%xmm0`レジスタの下位64bitだけで十分なはずである(実際、検証のためにプログラム書いて動かしてみたが`%xmm0`だけでやりとりをしていた)。
`long double`型の返り値を期待する場合は、[環境によって`long double`型のバイト数が異なる](https://qiita.com/mod_poppo/items/8860505f38e2997cd021)という理由で`objc_msgSend_fpret`関数が使われている。なので、`%xmm1` を`0`で初期化する意味は気になるところではある。まぁ、`__m256`型(256bit)とかあるので、そういうときの為だと思う。呼び出し規則を見ると一応 *`5. If the class is SSEUP, the eightbyte is passed in the upper half of the last used SSE register`* とあるので、レジスタ2つに分割するみたい。

これで動作として、以下の型に対しては、0を返すことが保証されていると言っても良い。

- `sizeof(void*)` : 64bit `%rax`を経由
- `float` : 32bit `%xmm0`を経由
- `double` : 64bit `%xmm0`を経由
- `long long` : 64bit `%rax`を経由

```nasm:
retq
```
`ret`はC言語でいう`return;`。これで関数が終わり、呼び出し元の次の命令にゆく。ということで、`objc_msgSend`関数を離れて、自分で書いた`objctest`関数に戻ってきた。

```nasm:
xorl   %ecx, %ecx
```
実際、この命令は`objctest`関数の返り値`0`を用意するためのもの。`void objctest()`で定義したら消えた。

```nasm:
movl   %eax, -0xc(%rbp)
```
`-0xc(%rbp)` は `rbp`レジスタの値から`0xc`減算した場所示すもので、変数bのアドレスである。`%eax`には、`objc_msgSend`関数からの返り値の`0`が入ってる。つまり、`objc_msgSend`関数からの返り値を変数bへ代入している。これでようやく、**`[nil intValue]` からエラー無く`0`が返ってきたってわけだ。**

# メッセージ式
オブジェクト指向言語を`Java`や`C++`から始めた人は、`Objective-C`の`[]`を使ったメソッドの呼び方に違和感を感じるはずである。

```objc:Objective-Cでのメソッド呼び出し
[obj method]
```

```java:Javaでのメソッド呼び出し
obj.method()
```

`Objective-C`独特のメソッドを呼び出すための`[]`をメッセージ式と呼ぶ。`Objective-C`では**「オブジェクトからメソッドを呼び出す」**のではなく**「オブジェクトにメソッドを呼ぶようにメッセージを送る**」形をとったためである。メッセージ式を使う言語には`Smalltalk`があり、`Objective-C`はそのを影響を受けてできている。

![Objective-C](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/7d61476c-6ee8-97e3-0f22-181e30d4b991.png)
画像は https://www.levenez.com/lang/ より使用

しかし、`Objective-C`は`C`言語と`Smalltalk`を元にして全く新しい言語を作ったわけでは無いのです。

> Objective-C 言語は C 言語の上に拡張された言語なので、C 言語がそのまま使えます。 より正確には、Objective-C の存在は言語仕様の拡張ではなく、コンパイラの拡張であり、最終的にコンパイラは Objective-C 言語のコードを C 言語のコードに変換して処理しています。 事実、追加された型や予約語も、事実上はヘッダファイルで #define や typedef によって定義されているに過ぎないのです。
http://wisdom.sakura.ne.jp/programming/objc/objc2.html より

だから、`[]`で書いたはずのメソッドの呼び出しが、lldbで確認すると`objc_msgSend`関数に置き換わっていたわけである。`objc_msgSend`関数は名の通り、オブジェクトにメッセージを送る関数で、これは、`Objective-C`では、メソッド呼び出しのことをメッセージ送信(send message)として扱っていることに由来する。
`Objective-C`はC言語の拡張なので、C言語だけで書き直すことができる。実装の確認に使った以下のプログラムは、

```objc:test2.m
#import<Foundation/NSString.h>

int objctest() {
    id a = nil;
    int b = [a intValue];
    return 0;
}

int main() {   
    return objctest();
}
```
次のように書き換えられる。

```c:test2.c
#include <objc/message.h>

int objctest() {
    id a = nil;
    int b = ((int (*)(id, SEL))objc_msgSend)(a, sel_registerName("intValue"));
    return 0;
}

int main() { return objctest(); }
```
`objc_msgSend`関数のプロトタイプ宣言が以下のようになっているため、`objc_msgSend`関数はキャストして使わなければならない。

```c:
void objc_msgSend(void)
```
`SEL`型の実体は文字列を示すポインタなので"intValue"をそのまま第二引数として渡しても良いが、一応`SEL`型にするために`sel_registerName`を使う。

```bash:
 % cc -O0 -g test2.c -lobjc
 % ./a.out                 
 % 
```

実行するとエラーなく終了する。コンパイラオプションに`-lobjc`をつけているが、これは`Objective-C`のランタイムである。このランタイムの中に、`objc_msgSend`関数などが実装されており、`Objective-C`で書かれたプログラムを支えている 。
`objc_msgSend`関数がどのように実装されているメソッドを呼び出すかは以下の記事で詳しく説明されている。

https://qiita.com/kunichiko/items/a0b8ce64518fbf3eac0a

また、ランタイムのソースコードは公式、非公式で公開されているため、`Objective-C`の挙動を確かめることができる。

https://opensource.apple.com/source/objc4/

https://github.com/apple-opensource/objc4

<!--
https://torus.github.io/objc-runtime-book/01-method-call

-sharp (補足) Null安全な言語もランタイムが介在しているのか？
`Objective-C`はランタイムに実装されている`objc_msgSend`関数で`nil`チェックされることにより、Null安全のように見える(実際そういう意図かは不明)。Null安全とは、Nullが原因で実行時エラーにならない仕組みである。`Objective-C`に取って代わった`Swift`はNull安全である。Nullの可能性があるオブジェクト(Nullable)に、オプショナルチェイニング演算子`?.`を用いてメソッドやプロパティを参照することで、オブジェクトがNullのときは、実行時エラーを発生させずnullを返すことができる。
`Objective-C`はランタイムがメソッドの呼び出しに介在することで、ユーザーもコンパイラも`nil`チェックをすることなくNull安全(みたいなもの)が実現されている。そこで、**Null安全な言語もランタイムによって、それが実現されているかが疑問となった。**

-sharp Swiftの場合
`Objective-C`の後継`Swift`でのNull安全の実装を確認してみる。

-->