day4_ポインタと文字列リテラル

配列として指定する文字列リテラルと、ポインタとして指定する文字列リテラルの違いについて

int func_pntstr() {

    char str[] = "LIST";

    char* strP = "POINTER";

    printf("%s\n", str); //LIST
    printf("%s\n", strP); //POINTER

        return 0;
}

正直やってることはおなじに見えるし、どっちでも良くね?とも思ったんですが、色々調べてみると異なるようだ。 本日の教材として相変わらずこちらにサイトを利用させていただいています。

programming.pc-note.net

違いとしては

・配列として利用する場合は、メモリ上に元々存在している文字列分のサイズを新たにメモリとして確保し、文字列を割り当てる。
・ポインタとして指定する場合は、ポインタのアドレスがそのメモリ上に元々存在している文字列のアドレス自体を指し示す。

とのこと。 配列で割り当てる場合、割り当てた文字を変更する場合は1文字ずつ書き換えるか、strcpy()といった関数で書き換える必要があるが ポインタ変数の場合は、新たに別の文字列をそのまま割り当てることが出来るらしい。

一方、文字列リテラルそのものを書き換えることはC言語では許されないらしく、ポインタ変数で割り当てられた文字列を1文字変更する、といったことはできないようだ。

なんとなく言葉は理解出来たがいまいちピンと来なかったので、例によってVisualStudioのアセンブリで確認してみる。 全部乗せると見にくいので必要なところだけピックアップ。

    30:     char str[] = "LIST";
00D85092 A1 DC 7B D8 00       mov         eax,dword ptr [string "LIST" (0D87BDCh)]  
00D85097 89 45 F0             mov         dword ptr [str],eax  
00D8509A 8A 0D E0 7B D8 00    mov         cl,byte ptr ds:[0D87BE0h]  
00D850A0 88 4D F4             mov         byte ptr [ebp-0Ch],cl  
    31:     
    32:     char* strP = "POINTER";
00D850A3 C7 45 E4 E4 7B D8 00 mov         dword ptr [strP],offset string "POINTER" (0D87BE4h) 
    33:     
    34:     printf("%s\n", str); //LIST
00D850AA 8D 45 F0             lea         eax,[str]  
00D850AD 50                   push        eax  
00D850AE 68 D0 7C D8 00       push        offset string "%s\n" (0D87CD0h)  
00D850B3 E8 8E BF FF FF       call        _printf (0D81046h)  
00D850B8 83 C4 08             add         esp,8  
    35:     printf("%s\n", strP); //POINTER
00D850BB 8B 45 E4             mov         eax,dword ptr [strP]  
00D850BE 50                   push        eax  
00D850BF 68 D0 7C D8 00       push        offset string "%s\n" (0D87CD0h)  
00D850C4 E8 7D BF FF FF       call        _printf (0D81046h)  
00D850C9 83 C4 08             add         esp,8  

なるほど、たしかに文字列リテラル"LIST"の場合、レジスタ経由で文字列自体をコピーして利用しているのに対し 文字列リテラル"POINTER"の場合は、文字列のアドレスをそのまま取得しているように思える。

おそらく文字列リテラルはPEファイルの変更不可能(Read Only)の領域に確保されており 一度コピーして新たに確保された領域内ではいじることはできるが、そのままの領域では変更ができない、ということなんだろう。

新たに別の文字列リテラルを割り当てることができる、というのはコンパイル時に新たに割り当てる文字列も変更不可能領域に保管されるため ポインタのアドレスを変更するだけで文字列の変更が実現可能になる、という解釈をしました。