①malloc
メモリを管理するというのは重要である。
メモリを管理しなくてもプログラムは作れる。
だが、オーバーフローの原因となったり柔軟性が無くなったりメモリを無駄に取ってしまったりと公開するには支障が出る。
そこで考え出されたのは「○○個までのデータが扱える」では無く「メモリが許す限りデータを扱える。」としたほうが魅力的なプログラムになる。
C言語の標準ライブラリとしてmalloc,realloc,free等がある。
malloc,reallocでメモリを確保、freeで開放するまでアクセス可能。
mallocの定義は
#include <stdlib.h> void *malloc(size_t size);
続いてfreeは
#include <stdlib.h> void free(void *ptr);
free()の括弧の中身はちょっと考えれば分かるがmallocで取った変数名を入れる。
malloc関数は戻り値がvoid*となっているので
p=malloc();
の左辺値pもポインタの必要がある。
mallocのメリットとしてsizeofが中に入れられるのでsizeの許す限りメモリを取れるということである。
デメリットとして動的に確保出来るがために事前にサイズを調べられないのが挙げられる。
また、メモリのところでもやったがヒープセグメントに入れられる。
reallocはreallocate、つまり内容はそのままでサイズを拡張したいときに使う。
縮小も出来るが余分な入りきらないデータは切れてしまうので注意が必要。
拡張にも通常はそのままのアドレスを返すがなにかの都合で使えなかったら別のアドレスを返すことになる。
#include <stdlib.h> void *realloc(void *ptr, size_t size);
指定領域をn個分確保したいときにはcalloc(cell allocate)を使う。
#include <stdlib.h> void *calloc(size_t n, size_t size);
malloc、reallocは初期化されないがcallocは0に初期化される。
malloc等で確保された領域はデメリットで前述したがサイズが確認できないのでプログラマが管理するしかない。スタックに比べれば少ないがヒープを足場にしたオーバーフローのexploitコードも存在する。その場合戻り値ではなくてヒープの下にある重要な関数がオーバーフローさせる目的となる。
(メモリレイアウトに依存するので大抵簡単にはいかない。)
ためしにオーバーフローさせてみる
#include<stdio.h> #include<stdlib.h> main(){ char *p; char *a="testtest"; p=malloc(3); strcpy(p,a); printf("%s\n",p); free(p); } 結果 testtest
見ての通りオーバーフロー起こしても平気な顔で続くので注意。
また、freeによる弊害も挙げておこう。
#include<stdio.h> #include<stdlib.h> #include<string.h> main(){ char *a="testtest"; char *b; char *c; b=malloc(20); strcpy(b,a); free(b); c=malloc(20); strcpy(c,"ABCDEF"); printf("b=%s c=%s\n",b,c); free(c); return 0; } 結果 b=ABCDEF c=ABCDEF
一見すると正しいコードのように見えますがfreeしたbの上にさらにmallocが入り、
結果としてbの値を上書きしてしまっている。
freeは注意深く出来れば確実に使わない関数の終わりのほうでするべきである。
reallocはreとついている以上malloc,calloc,reallocのいづれかで返されたアドレスしか使えないが例外としてnullが入っていたときだけmallocと同じ働きをする。
#include<stdlib.h> main(){ char *bufp=NULL; int c; int size=0; while(1){ c=getc(stdin); bufp=realloc(bufp,size+1); if(size<=10){ bufp[size]=c; size++; } else{ bufp[size]='\0'; break; } } if(bufp){ puts(bufp); free(bufp); } return 0; }
このように色々問題があるがエラー無くコンパイル出来る。
例としてのっていたコードを載せる
#include<stdio.h> #include<stdlib.h> #include<assert.h> #define ALLOCSIZE 512 int main(void) { char *bufp=NULL; char *newp=NULL; int c; int mem_size=0; int real_size=0; while(1){ c=getc(stdin); if(mem_size<=real_size) { mem_size+=ALLOCSIZE;//*一度に拡張するサイズ*// if(NULL==(newp=realloc(bufp,mem_size))) { fprintf(stderr,"メモリがない"); break; } bufp=newp; } assert(real_size<mem_size); if(c!=EOF) { bufp[real_size]=c; real_size++; } else { bufp[real_size]='\0'; break; } } if(bufp){ puts(bufp); free(bufp); } return 0; }
というか未だにEOFの使い方が曖昧・・・
メモリリーク
mallocしたデータをfreeしないで放って置いたら上書きしたmallocのせいでアクセス出来なくなった。
この様な状況をメモリリークと呼ぶ。
#include<stdio.h> #include<stdlib.h> #include<string.h> main(){ char *p; p=malloc(256); strcpy(p,"testtest"); p=malloc(256); strcpy(p,"ABCDEF"); printf("p=%s\np=%s\n",p,p); free(p); } 結果 p=ABCDEF p=ABCDEF
このようになってもはやtesttestという文字列はアクセスする手段がない。
mallocとstrlen
カーニハンの「プログラミング作法」によると慣用句をなるべく多用すべきとある。
慣習は誤りが無いからだ。
strlen分だけmallocしたいときに良く起こす間違いは
#include<stdio.h> #include<stdlib.h> #include<string.h> main(){ char *p,buf[256]; gets(buf); p=malloc(strlen(buf)); strcpy(p,buf); puts(p); }
こんなコードだという。
結果は正常に動く。
だがstrlenは'\0'を勘定に入れない長さをはじき出すがstrcpyはそれもコピーしてしまうのだ。
この場合の慣用句は
p=malloc(strlen(buf)+1);
strcpy(p,buf);
と書けばよい。
因みにこの本ではgetsをいかなるときも使ってはいけないと書いてある。
なぜならgetsには制限が無くセキュリティ上都合が悪いからだ。
getsは必ずfgetsに直して使うようにする。