はじめに
C言語だとメモリを動的確保する際に、mallocやcallocといったメモリを確保する関数を使うと思います。そのときの注意です。
基本的な使い方に関してはこちら
callocの返り値はアドレス
gcc -v
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.3 (clang-1103.0.32.59)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
本文
「stdlib.hを必ずインクルードすること」
mallocを使うためには、#include <stdlib.h>
を魔法のように唱えると思います。
では唱えなかった場合はどうなるでしょうか。普通ならコンパイル(プリプロセス)の段階で落ちるはずです。ですがmallocに関してはwarningを吐くだけでコンパイルはできてしまいます。
この時のデメリットとして、返り値がint型になることが挙げられます
「想定した動作では64bitのlong*(long型ポインタ)として 返したいにもかかわらず、32bitのint型が返ってしまう。」というようなバグが起こるわけです。
参考:mallocのwiki > C言語での動的メモリアロケーション
「要素数 0 でcallocを呼ばないこと」
callocは通常、配列などの複数要素を動的に確保するために使用するケースが多いです。なので、本来要素数を0で呼ぶことは考えられないのですが、呼んでしまった場合どうなるでしょう
メモリ確保に失敗したとしてNULLポインタが返って...くるわけではありません。
実際には処理系定義ですが、返せる領域があればNULLポインタではなくアドレスが返ってくる場合が存在します。
その場合、NULLだと思って処理を進めるとバグを生む可能性があります。
参考:C 標準 [ISO/IEC 9899:2011] セクション 7.22.3 抜粋ページ
「確保した要素数以上へのアクセスはできてしまう」
先ほどに引き続き、callocは複数要素を動的に確保するために使用するケースが多いです。なのでint型の配列で3要素欲しければ(int*)calloc(3, sizeof(int))
をint* の変数に代入するわけです。
配列の宣言としてint array[3]
としたときarray[3]
にアクセスするとどうなるかもちろん参照外アクセスで落ちますね。
ではcalloc関数の場合は?
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]){
int* ap;
ap = calloc(3, sizeof(int));
ap[2] = 1;
ap[3] = 1;
printf("%p\n", &ap[2]); // アドレスを表示
printf("%p\n", &ap[3]);
printf("%p\n", &ap[4]);
printf("%d\n", ap[2]);
printf("%d\n", ap[3]);
printf("%d\n", ap[4]);
free(ap);
return 0;
}
0x7fe2d3c05848
0x7fe2d3c0584c
0x7fe2d3c05850
1
1
0
普通にアクセスできるですね(ドン引き)。しかもコンパイラはwarning出しませんでした。
ここからは予想ですが、callocは起点となるアドレス(ar[0])を返すだけですから、連続領域として確保した要素数以上のところもアクセスできてしまうんですかね(謎)
いずれにしても、こういう想定外のことはバグの原因になるので注意しましょう。