値の変更が出来る文字列についての格闘期。

ちょっとはまって調べてみたら、もっとはまってしまった・・・。値の変更が出来る文字列についての格闘記。

値の変更について

func(char *s) {
 *s = 'a'
}

func("moge");

というアホなプログラムを書いてしまった・・・値が変更が出来る訳が無い!!

なんとなく気になったので、値の変更について調べることにした。

値の変更について調べてみた。

strcpyで値をコピーしてみる。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char s1[] = "hoge";
    const char s2[] = "piyo";

    /* 普通 */
    strcpy(s1, "copy");
    printf("%s\n", s1);

    /* 警告は出るけど値の変更はできる */
    strcpy(s2, "copy");
    printf("%s\n", s2);

    /* 値の変更はできない (mogeはダミー) */
    strcpy("fuga", "moge");

    exit(EXIT_SUCCESS);
}

テスト。

% ./str-test
copy
copy
zsh: segmentation fault  ./str-test
  • const charだと警告は出るけど値の変更は可能。
  • 直接"fuga"と書くと値の変更は出来ない。segmentation faultが発生する。

ここまでは感覚的にわかる。「感覚」ってのが困るので確かめる。

感覚を実感に変えたい。

printf("%p")で、ポインタアドレスを見ることが出来る。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char s1[] = "hoge";
    const char s2[] = "piyo";

    printf("%p\n", s1);
    printf("%p\n", s2);
    printf("%p\n", "fuga");

    exit(EXIT_SUCCESS);
}

実行する。

0xbfb73047
0xbfb73042
0x8048594

メモリ上では全く違う所に配置されてる。constも書き込み可能領域に書き込まれるみたい。

「何処に配置されたのか」というのがイマイチわからない。

objdumpしてみる。

で、こっからが謎の部分。objdumpしてみた。

% objdump -s -j .rodata str-test
 8048584 03000000 01000200 00000000 25700a00  ............%p..
 8048594 66756761 00636f70 7900686f 67650070  fuga.copy.hoge.p
 80485a4 69796f00                             iyo.

0x8048594が一致してた。おぉぉぉ。

でも、でもだ。同じ所に配置されてる!!

rodataは"read only data"かな・・・。ってことは・・・こっから値をメモリにコピーして・・・んじゃぁ、

s1,s2周辺ににfugaがあるのかなぁ・・・と思ってメモリを探してみたけど見当たらず。


うぅぅぅん。なんで同じ所に配置されてるんだぁ!!

お?

あぁ、スタックだ。スタック上に配置したのかも。

と思って、確認する。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char s1[] = "hoge";
    const char s2[] = "piyo";
    char *p = "moge";       // ポインタも追加する。
    int i;                  // スタック領域確認用

    printf("%p\n", &i);     // スタック領域確認用
    printf("%p\n", s1);     // char[]
    printf("%p\n", s2);     // const char[]

    printf("%p\n", "fuga"); // "fuga"
    printf("%p\n", p);      // char *

    *p = 'a';               // ??

    exit(EXIT_SUCCESS);
}

ポインタの文字列も追加してみた。

実行する。

0xbff47c10  // &i
0xbff47c1b  // char[]
0xbff47c16  // const char[]
0x8048519   // "fuga"
0x8048510   // char *
zsh: segmentation fault //*p = 'a'

配列は、&iと同じ位置なので、スタック領域に格納される事がわかる。

ポインタで格納された文字列と、配列で格納された文字列では全く違う所を参照している!!

しかも、ポインタで格納した文字列は"fuga"と同じ「.rodata」にある!!

まとめ

  • 文字列はrodataに格納されてる。.rodataはたぶん「read only data」
  • 配列の場合、値をスタック領域にコピーして格納される。
  • 直接値を参照したり、ポインタで文字列を指定すると、.rodataの値を参照する。

つまり、

char *p = "moge";
*p = 'a';

とした場合、書き込み禁止領域にあるので、値の変更は出来ない。よって、segmentation faultが発生する。


配列とポインタ文字列が「別物」ということが実感に変わった。

アホなプログラムから始まったけど、良い勉強できた。調べてみるもんだね。