目次
リンク集
feof()
や ferror()
の使用方法について言及されている。
ヘッダーファイルについて
以下のようにすれば、コンパイラがデフォルトで検索するヘッダファイルとライブラリの場所を検索できる:gcc -print-search-dirs icc -print-search-dirsこちらを参照にすれば、ディレクトリを増やせる (かも)
my_malloc, my_free のインストール例
- my_malloc.c、my_malloc_free.h を /usr/local/include/ あたりに保存
- /usr/local/include/ に移動して root 権限を取得 (root じゃない場合をまだ考えてなかった...)
- gcc -c my_malloc.c
- gcc -fPIC -shared -o ../lib/libmalloc.so my_malloc.c
- chmod 644 ../lib/libmalloc.so
- (必要に応じて、~/.bashrc に extern LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib を追加して再読み込み)
- C ソースに #include <my_malloc_free.h> を記述
- gcc では -lmalloc というオプションをつける (libmalloc.so の lib を l にした名前である)
malloc 関数・free 関数 (メモリの動的確保・解放) に関して
C言語初心者ですので、このページが間違ってて、これを信じて損害を被ったとしても、責任は取りません。ただ、間違いを指摘して頂けると助かります。
malloc 関数の基本形
free 関数の基本形
malloc 関数の注意事項
- malloc 関数は、プログラムが占有できるメモリを確保して、そのメモリのアドレスをポインタに与える、という関数である
- ゆえに、malloc 関数を2重で使ってはいけない
- ゆえに、malloc 関数を2重で使ってはいけない
-
malloc 関数の直後に、ポインタが NULL でないか確認すべし (メモリの確保に失敗した場合に備えよう)
free 関数の注意事項
- free 関数は、ポインタが指し示すアドレスに存在していた、malloc などによって動的に確保 (プログラムが占有) していたメモリを、他のプログラムが占有してよい状態にする「だけ」 (これを「解放する」と言っている)
- しかし、ポインタの指し示すメモリのアドレスは free 関数だけでは変化しない
- free() は、引数に指定されたポインタが指し示すアドレスに存在していた、malloc() などによって動的に確保 (そのプログラムが占有) していたメモリを、他のプログラムが占有してもいい状態にする
- しかし、ポインタが指し示すアドレスの位置は free() だけでは変わらない
- 1度「他のプログラムが占有してもいい状態」になったメモリを、再度 free() することはできない
-
同じ理由で、「初期化 (or malloc) を行っていない、宣言したばかりのポインタ」を free するのも危険である
- なお、NULL を free しても何も起きないことは保証されている
- free の直後にポインタに NULL を代入すれば2重解放は防げるし、初期化の時に NULL を代入してしまえば、宣言したばかりのポインタの解放もしなくて済む (ことが多いと思われる、くらいにしておきます、絶対の自信はないので...)
- free の直後にポインタに NULL を代入すれば2重解放は防げるし、初期化の時に NULL を代入してしまえば、宣言したばかりのポインタの解放もしなくて済む (ことが多いと思われる、くらいにしておきます、絶対の自信はないので...)
- 結局、宣言直後のポインタや、メモリを解放したポインタには NULL ポインタを代入し、ポインタに与えられていたアドレスの情報を「忘れさせる」ことで、「他のプログラムが使っているメモリを勝手に解放してしまう」事故の予防ができる
-
しかし、複数のポインタが共通するアドレスを指す場合は要注意: あるポインタからメモリを解放し、そのポインタに NULL ポインタを代入したとしても、他のポインタには NULL を代入しない限り、解放したはずのメモリが残っているので、結局そのメモリにアクセスできてしまい、2重解放などの危険性 (他のプログラムが使っているメモリの書き換えや解放など) がある
- 下記プログラムでは、a[0] と a_1dim が同じアドレスを指しているので、
a[0] (が指すアドレスにあるメモリ) を解放した場合、
a_1dim (が指すアドレスにあるメモリ) も解放されていることになる。
解放後、ポインタ a[0] に NULL を代入し、
解放したメモリのアドレスを「忘れさせた」。
しかし、ポインタ a_1dim はまだアドレスを「覚えている」ため、下記プログラムでは2重解放が発生している。
-
下記のように、メモリを確保する部分を別の関数として分けてしまえば、呼び出し元の関数において、複数のポインタが同じアドレスを指す状況は回避できる:
- 下記プログラムでは、a[0] と a_1dim が同じアドレスを指しているので、
a[0] (が指すアドレスにあるメモリ) を解放した場合、
a_1dim (が指すアドレスにあるメモリ) も解放されていることになる。
解放後、ポインタ a[0] に NULL を代入し、
解放したメモリのアドレスを「忘れさせた」。
しかし、ポインタ a_1dim はまだアドレスを「覚えている」ため、下記プログラムでは2重解放が発生している。
-
しかし、複数のポインタが共通するアドレスを指す場合は要注意: あるポインタからメモリを解放し、そのポインタに NULL ポインタを代入したとしても、他のポインタには NULL を代入しない限り、解放したはずのメモリが残っているので、結局そのメモリにアクセスできてしまい、2重解放などの危険性 (他のプログラムが使っているメモリの書き換えや解放など) がある
my_malloc マクロ, my_free マクロ
以下のマクロを用意した-
このマクロの特徴は以下の通り:
- NULL ポインタを用いて、malloc 関数の2重使用 (上記 malloc 関数の悪い使い方の例) の防止ができる。
- malloc 関数が成功したか確認できる (上記 malloc 関数の良い例)
- free 関数の直後に NULL ポインタの代入を行っている
-
このマクロでエラーとなるのは以下の場合
- ポインタを宣言する時に NULL で初期化していない場合 (上記 free 関数の悪い使い方の例2)
- このエラーは、my_malloc の最初に free 関数を使っていることに起因する (通常の malloc 関数では起こらない)
my_malloc マクロ, my_free マクロの使用例
以下のように行えば、上記の malloc, free 関数に伴うリスクを軽減できる:malloc 関数、free 関数のまとめ
2つの関数をまとめると- malloc 関数はプログラムが占有できるメモリを確保して、 そのメモリのアドレスをポインタに与える関数。
- free 関数は、ポインタが指し示すアドレスにあるメモリを、 他のプログラムが占有してもよい状態にする関数。
続いて、諸注意をまとめると
-
free 関数でメモリを解放した後も、ポインタが指し示すアドレスは変わらない。
-
そのため、そのアドレスにあるメモリが他のプログラムによって使われているかもしれない。
- したがって、あるポインタに free 関数を用いてメモリを解放した後は、そのポインタの指し示すアドレスに存在するメモリにアクセスしないように気を付ける。
- 2重解放がいけないのも、このためである。
-
解放と同時にポインタに NULL を代入すれば (my_free マクロ)、アドレスの情報が失われ、使わなくなった解放済のメモリにアクセスできなくなる (free 関数も無効化される) ので、安全である。
- ただし、解放されたメモリのアドレスと同じアドレスを指し示すポインタが他にも存在する場合は、そのポインタを用いてのメモリのアクセスもしないように、注意を要する (上記の、行列を動的に確保した場合の例のように、関数を分けることでアクセスできなくしたりする工夫もある)。
-
そのため、そのアドレスにあるメモリが他のプログラムによって使われているかもしれない。
-
malloc 関数を2重で使ってしまうと、最初に malloc で確保したメモリのアドレスが、後の malloc で確保したメモリによって上書きされてしまう
- そのため、最初の malloc で確保したメモリにはアクセスできなくなってしまう。
-
ポインタが NULL である場合のみに malloc を許可するようにすれば、先に確保していたメモリを指し示すアドレスが上書きされることが減り、安全となる。
- これを行うにあたって、ポインタの宣言時点で NULL で初期化を行うことを徹底する。
- なお、このアドレスの上書きによって、元々確保していたメモリにアクセスできなくなるリスクは malloc に限らず、ポインタを戻り値にする全ての関数に共通である。
-
malloc 関数は失敗時に NULL を返すので、戻り値の確認をすることを徹底する。
- エラーの場合にメモリをきちんと解放処理したい場合はC言語におけるgoto文についての雑感 (外部サイト) などを参照
- 上記サイトのバックアップ: こちら (要 password)
sscanf 関数について
sscanf 関数の基本形
sscanf 関数のフォーマット
%[代入抑止][フィールド幅][h/l/L][変換文字]- 代入抑止(省略可): 「*」を入れると代入を抑止する
- フィールド幅: %c (常に1文字), %n (文字数の概念がない) 以外の全ての指定子に対して最大フィールド幅 (変換される最大の文字数) を指定できる
-
h/l/L: 読み取る数値の精度を規定する
-
h: 読み取る引数が 2 バイト (環境によって変わるかもしれない) の数値、あるいは2バイトの数値へのポインタであることを示す。
- 変換文字が d, i, o, u, x, X のとき、対応する引数が short int または unsigned short int であることを示す。
- 変換文字が p のとき、対応する引数が short int へのポインタであることを示す。
-
l: 読み取る引数が 8 バイト (環境によって変わるかもしれない) の数値、あるいは 8 バイトの数値へのポインタであることを示す。
- 変換文字が d, i, o, u, x, X のとき、対応する引数が long int または unsigned long int であることを示す。
- 変換文字が p のとき、対応する引数が long int へのポインタであることを示す。
- 変換文字が e, E, f, g, G のとき対応する引数が double であることを示す。
- L: 読み取る引数が 16 バイト (環境によって変わるかもしれない) の数値、あるいは 16 バイトの数値へのポインタであることを示す。
- 変換文字が e, E, f, g, G のとき対応する引数が long double であることを示す。
- 変換文字が p のとき対応する引数が long double へのポインタであることを示す。
-
h: 読み取る引数が 2 バイト (環境によって変わるかもしれない) の数値、あるいは2バイトの数値へのポインタであることを示す。
-
変換文字: 入力書式を規定する
-
%c
: 文字 (空白を含む)。フィールド幅で示した個数を読み込み、指定した配列に入れる。配列の最後にヌル文字は付加されない。 -
%d
,%i
: 10進整数 -
%e
,%E
,%f
,%g
,%G
: 浮動小数点数 (指数文字の大文字/小文字は区別しない) -
%o
: 8進数 -
%s
: 文字列 -
%x
,%X
: 16進数 -
%p
: ポインタ -
%n
: これまで読み込まれた文字の数に等しい整数値を受け取る-
こういう使い方ができる (かも?):
int c = 0; char *p; char s1[], s2[]; sscanf(p, "%10s%n", s1, &c); /* c は入力が10文字以上なら 10, 未満なら読み込んだ文字数になる */ p += c; /* p を c 文字だけインクリメント */ sscanf(p, (*p == ' ') ? "%s" : "%*s %s", s2); /* *p が空白文字ならそのまま %s を読む (s1 が 10 文字以下の場合), 空白以外の場合は一旦読み飛ばしてから次の文字列を読む (s1 が 11 文字以上の場合) */
-
こういう使い方ができる (かも?):
-
%u
: 符号なし10進整数 -
%%
: 入力された「%」文字を読み捨てる (代入と変換は行わない) -
%[]
: 文字の集合 (スキャン集合 / scanset という)-
%[ABC]
: 'A' または 'B' または 'C' からなる文字列 (入力がこれら3文字である限り読み込みを1文字ずつ続ける) -
%[^ABC]
: 'A', 'B', 'C' 以外からなる文字列 (入力がこれら3文字以外である限り読み込みを1文字ずつ続ける) -
%[A-C]
: 'A' から 'C' までの範囲の文字のみからなる文字列 (入力がこの範囲内の文字である限り読みこみを1文字ずつ続ける (注: この範囲指定子は ANSI C標準には規定されていない (がほとんどのコンパイラで有効である)) -
結局、まとめると以下の通り:
-
「
%[...]
」をスキャン集合という (以下、赤文字「%
」、「[
」、「]
」、「^
」、「-
」はスキャン集合における特殊文字を表す。特殊文字ではなく単なる文字としての「%
」、「[
」、「]
」、「^
」、「-
」は黒字で表す)。 -
一般に、スキャン集合は「
%[^](その他の文字)-]
」の形で書くのがよいと思う (「^
」、「]
」、「(その他の文字)
」、「-
」は必須ではない)。各要素の説明を以下に行う:-
「
%[
」: スキャン集合の始まり -
「
^
」(なくてもよい): この先頭の文字「^
」はあってもなくてもよいが、「^
」がある場合、スキャン集合「%[^](その他の文字)-]
」は、スキャン集合「%[](その他の文字)-]
」の補集合を表す。 -
「
]
」(なくてもよい): 文字としての「]
」を書きたい場合は、この位置に記述する (そうじゃない場合、スキャン集合の終わりの「]
」と見なされる (可能性が高い))。 -
「
(その他の文字)
」: 特殊記号と紛らわしい「]
」、「-
」以外の文字 (文字としての「^
」も含む) はここに記述する。多くのコンパイラはこの部分に含まれる「-
」を範囲指定子として認識する。 -
「
-
」(なくてもよい): 文字としての「-
」を書きたい場合は、この位置に記述する (そうじゃない場合、多くのコンパイラは範囲指定子としての「-
」として認識する (可能性が高い))。 -
「
]
」: スキャン集合の終端
-
「
-
なお、「
%[^]
」では「^
」のみからなる文字列としてのスキャン集合を表せないようだ。-
「
%[^]
」において、「^
」はスキャン集合の補集合を表す文字として認識され、直後の「]
」は (スキャン集合の終端の「]
」ではなく) 文字としての「]
」として認識される。このため、「スキャン集合が閉じていない」旨のコンパイルエラーが生じる。 -
ASCII コードにおいて、(印字されない制御文字を除くと) 最初の文字は「
!
」、「^
」の2つ前は「\
」(エスケープすると「\\
)、「^
」の1つ前は「]
」、「^
」の1つ後は「_
」、最後は「~
」なので、「%[^]!-\\_-~]
」という表記も考えられるが、この表記はタブ文字やヌル文字、改行文字なども含めてしまうので、危険である。
-
「
-
「
-
-
sscanf 関数のフィールド幅の指定の例
sscanf にフォーマット指定子以外を入れた場合
その文字まで正確に入力しなくてはならなくなり、注意を要する。この場合の厳密な処理の流れについてはよく分からない (情報を求む)。Openmp について (書きかけ、検証なし)
サンプルプログラム1
(注: 2020/5/18 時点で /usr/lib/gcc/x86_64-linux-gnu/7/include/ に omp.h があることを確認済)-
プログラム (sample_omp.c):
- コンパイル & 実行結果1:
kentagch@sgr:~/public_html/c/test_omp$ gcc -o sample_omp.exe sample_omp.c -fopenmp kentagch@sgr:~/public_html/c/test_omp$ ./sample_omp.exe This is thread 0 of 4 This is thread 3 of 4 This is thread 1 of 4 This is thread 2 of 4
- コンパイル & 実行結果2:
kentagch@sgr:~/public_html/c/test_omp$ gcc -o sample_no_omp.exe sample_omp.c kentagch@sgr:~/public_html/c/test_omp$ ./sample_no_omp.exe No OpenMP