文字列リテラルの配列への代入

gcc で -Wformat-nonliteral の確認をしていたときに、文字列リテラルについてちょっと自分が誤解していたことに気づいた。文字列リテラルの代入は(善し悪しはともかく)次のようなバリエーションがある。

 char string0[] = "test";
 const char string1[] = "test";
 char* string2 = "test";
 const char* string3 = "test";

この場合、string2, string3 だけではなく、string1 も const char なので .rodata にあるリテラルの実体のポインタが利用されるのかと思っていた。しかし実際には string0 と同様に string1 もスタック上に確保された領域にコピーされていた。

#include <stdio.h>

int main(int argc, char** argv)
{
    const char string[] = "test";
    printf(string);

    return 0;
}

は、gcc 4.1.2 の -O2 -S で以下のようにコンパイルされる。

        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "test"
        .text
; snip
        movl    .LC0, %eax      ; この辺がコピー処理
        movl    %eax, -9(%ebp)  ; (しかし movl なのかyp)
        movzbl  .LC0+4, %eax    ;
        movb    %al, -5(%ebp)   ;
        leal    -9(%ebp), %eax
        movl    %eax, (%esp)
        call    printf
; snip

ところで、

#include <stdio.h>

int main(int argc, char** argv)
{
    char* string = "test";
    string[0] = 'X'; // ココ
    printf(string);

    return 0;
}

コンパイルエラーはないが、-O1 or -O2 だと "test" が表示され、-O0 だとセグメンテーションフォルトになる。-O0 の挙動は string は文字列リテラルのポインタを指しているので .rodata に書き込もうとして落ちるのだが、-O1 or -O2 では代入処理が最適化で消えてしまうのはなぜだろう?