値の変更が出来る文字列についての格闘期。
ちょっとはまって調べてみたら、もっとはまってしまった・・・。値の変更が出来る文字列についての格闘記。
値の変更について
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が発生する。
配列とポインタ文字列が「別物」ということが実感に変わった。
アホなプログラムから始まったけど、良い勉強できた。調べてみるもんだね。