技術日誌

DB,Java,セキュリティ,機械学習など。興味のあることを雑多に学ぶ

C言語の復習その2ポインタ

  • 各型に対するポインタ変数の型がある
  • どのかたのポインタも同じサイズ(私の環境では8バイト)
  • 構造体のポインタ型もある
  • ポインタ変数型のポインタもある
    char c = 'c';
    char* pc = &c;
    char **ppc = &pc;

    printf("-------------------\n");
    printf("c value is %x\n",c);     //63 = asciiで'c'
    printf("pc value is %x\n",pc);   //ec1daabf
    printf("ppc value is %x\n",ppc); //ec1daab0

    printf("-------------------\n");
    printf("c addr(&c) is %x\n",&c);       //ec1daabf
    printf("pc addr(&pc) is %x\n",&pc);    //ec1daab0
    printf("ppc addr(&ppc) is %x\n",&ppc); //ec1daa98

    printf("-------------------\n");
    printf("c reference is nothing\n");         //なし
    printf("pc reference(*pc) is %x\n",*pc);    //63
    printf("ppc reference(*ppc) is %x\n",*ppc); //ec1daabf

    printf("-------------------\n");
    printf("ppc referencese reference(**ppc) is %x\n",**ppc);//63

この例を表に起こすとこんな感じ

変数 アドレス ポインタ参照(*変数) ポインタ参照の参照(**変数)
c char 0x63 0xec1daabf なし なし
pc char* 0xec1daabf 0xec1daab0 0x63 なし
pcc char** 0xec1daab0 0xec1daa98 ec1daabf 0x63
  • 大きな配列をローカル変数として宣言するとスタックを使い過ぎてしまう。

    • コンパイラは特にこの件に関して警告を出さない
    • 対策
      • コンパイル時にスタックの領域を大きくする
      • staticやグローバル変数を使うようにしてスタックを使わなくする
      • mallocを使い明示的にメモリを確保する
      • メモリ上の変数を使わない(ファイルで扱う)
  • C言語では関数には値そのものではなく、値のコピーが渡るらしい。

    • アセンブラ(objdump)で見て見たけど、よくわからなんかった
    • 配列を渡すと、配列のアドレス(のコピー)が渡る
      • 配列のコピーを渡す方法はない。
  • ポインタの正確な定義は難解らしい

    ポインタという言葉の定義は、実はあんまりはっきりとは決まってないらしい 注2-A正確にいうとANSIの仕様として正確に決まってはいます。が、それを定めた文章はあまりに難解で、とてもではありませんが簡単に理解できるようなものではありません。そのせいもあるでしょうが、一般的には「ポインタ」という言葉の定義は、ないに等しい状態です。p105

  • ポインタという呼称はややこしいのでやめた方が良い

    • 文脈に応じて
      • ポインタ変数
      • アドレス
    • に言い換えること。p106
  • 配列を引数として受け取る関数の宣言

    • void func(int *arg)と書くのが一般的
    • void func(int arg[])と書くこともできる。
    • 俺は後者の方が好き。
  • %pの出力に関して。

    • 6Byteのものが表示された。
      • 0x7ffee878ea86
    • これは何かのオフセット?
    • 一瞬ページングのページオフセットかと思ったけどあっちは12bit
    • 物理アドレスではないと思う。
  • ポインタの添字とラベルを変えることができる豆知識の説明

    • i[5]5[i]と同じ意味
    • OS自作本ではアセンブリのレベルで説明していた
    • この本ではポインタアクセスの中身を加算にして説明している
      • i[5]=*(a+5)=*(5+a)=5[a]
  • 2次配列はただの配列の配列

  • 2次元配列はアドレスで受け取れない
    • void func(int arg[])=void func(int *arg)
    • void func(int arg[3][5]) = void func(int arg[][5])
      • void func(int *arg)これで多次元配列は受け取れない
      • 配列の関数が受け取るときには、要素数は最初の要素数だけが省略できる(p117より)

    • なぜか本書の内容を検証したがうまくいかなかった。
//OK
void func1(int array[3][5]){
  puts(array);
  puts(array[1]);
}

//OK
void func2(int array[][5]){
  puts(array);
  puts(array[1]);
}

//不正
void func3(int *array){
  puts(array);
  puts(array[1]);
}

void main(){

  func1(color);
  func2(color);
  func3(color);

}

結果

Blue

Blue

Blue
Segmentation fault: 11

うーん最後のセグフォは納得だけど、Grayが渡せていない?

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み