「はじめて学ぶCの仕組み2」を読んだ
大体1週間程度で読み終わりました。
プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み
- 作者: 倉薫
- 出版社/メーカー: 翔泳社
- 発売日: 2009/02/13
- メディア: 大型本
- 購入: 2人 クリック: 6回
- この商品を含むブログ (3件) を見る
この本を読み始めた動機は
※C++の本を開いた理由は、大局的にはオブジェクト指向の機能がある言語でOSを読み書きしようという野望があったからです。
- 作者: グレゴリーサティア,ダウグブラウン,Gregory Satir,Doug Brown,望月康司,谷口功
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2001/11/01
- メディア: 単行本
- 購入: 9人 クリック: 147回
- この商品を含むブログ (30件) を見る
よかったところ
C言語から手続き型プログラミングを引いた部分を一通り学べる
などの使い方について一通り学べます。
if文やfor文とかに関する長い説明はいらないけど、 C言語の基本的な機能に自信がない・・・という私みたいな人にぴったりな本でした。
よくなかったところ
- 演習問題があまり充実していない。
- volatileの説明がない。まぁそもそもあまり使わないものらしいが。
- 構造体を
->
で利用する方法が書かれていない
わからなかったところ
配列が持っている長さ(添え字)の情報
char s[5];
この分の添え字が結局何のためにあるのかよくわかっていません。
char s[5] = "hello"; puts(s);
これは変数をヒープに割り当てる際に、 s[0]のアドレスから変数型*5バイト分後ろがsの領域になるように割り当てているということでしょうか。
この添え字は多分変数s
の領域の確保時に、以前確保した変数と被らないように領域を確保するだけに思います。
実行時はこの配列s
への書き込み以外の方法でも書き込みに対して保護などされておらず、上書きされるように思います。
例えばs
の前後に配列を確保し、アドレスがかぶるように不正なindexを設定して代入するなどして簡単にメモリ破壊ができるようです。
char a[1]; char s[6] = "hello\0"; char b[1]; puts(s); //hello printf("a at:%p\n",a); printf("s at:%p\n",s); printf("b at:%p\n",b); // a at:0x7ffee9992acf // s at:0x7ffee9992ac9 // b at:0x7ffee9992ac8 printf("a[1] at:%p\n",&a[1]); printf("s[1] at:%p\n",&s[1]); printf("b[1] at:%p\n",&b[1]); // a[1] at:0x7ffee9992ad0 // s[1] at:0x7ffee9992aca // b[1] at:0x7ffee9992ac9 //この環境だとbはsの1バイト小さいアドレスに確保される //b+1のアドレスはsと一致している //注意)b[0]までが正規の範囲 //コンパイル時に警告 // test.c:32:3: warning: array index 1 is past the end of the array (which contains // 1 element) [-Warray-bounds] // b[1] = 'x'; // ^ ~ // test.c:7:3: note: array 'b' declared here // char b[1]; // ^ b[1] = 'x'; //*(b+1) = 'x'; //←こんか感じでもアクセスできる puts(s); // xello }
変数を確保するときだけ使っているように思えます。
この本にはいわゆる「C言語は自分の足を打てる言語」的なエッセンスは書かれていません。
文字列リテラルは3つの例外を除いてポインタになっている
「sizeof演算子のオペランドの場合(読み替えが抑止され、配列全体のサイズが返される)」「&演算子のオペランドの場合(配列全体へのポインタが返される)」「配列初期化時の文字列リテラル(文字列リテラルは普通はcharの配列だが、この場合特別に初期化子の省略形とみなされる)」です。
— hsjoihs@数情物化語 (@hsjoihs) 2018年6月5日
わからないというか、そもそもきちんと検証していなかったので検証してた。
「sizeof演算子のオペランドの場合(読み替えが抑止され、配列全体のサイズが返される)」
printf("%lu\n",sizeof "test");//5
「&演算子のオペランドの場合(配列全体へのポインタが返される)」
printf("%p\n", &"test"); printf("%p\n", "test"); //出力は同じ値になる // 0x1052edfb3 // 0x1052edfb3
「配列初期化時の文字列リテラル(文字列リテラルは普通はcharの配列だが、この場合特別に初期化子の省略形とみなされる)」です。
char test0[5]; char test1[5] = {'t','e','s','t'}; char test2[5] = "test"; char *test3; test3 = "test"; //宣言と代入を分けているので、静的な領域に確保した"test"という文字列のアドレスを //格納したことになっていると思う。 printf("%p\n",test0); printf("%p\n",test1); printf("%p\n",test2); printf("%p\n",test3); //test3だけアドレスが遠い // 0x7ffee95c8acb // 0x7ffee95c8ac6 // 0x7ffee95c8ac1 // 0x106637fab
規格
規格と実装の差分
C11やC99があるようですが、こちらの具体的な紹介などはありませんでした。 言語としてコアな部分は変わっていないと思いますが、 本書の感想をブログに投稿中は、標準規格ではこうなっている、ということを教えてもらうことが多かったです。
ちなみに色々ネットや本を見ているとC++では結構規格が重要になってくるようです。C++11,C++14,C++17などあるようですが、
- このバージョン以前はコードがかなり煩雑になる。
- あるバージョンでシンプルにコードを書く方法が入ったので、時代遅れの方法を使うべきではない等。