Hatena::ブログ(Diary)

わさっき RSSフィード

2012年02月19日

[][] calloc

今月上旬のことです.試験の問題作成をしていて,確認のため教科書を開くと,「かけ算の順序」が目に飛び込んできました.

f:id:takehikom:20120219055209j:image

C言語クイック入門&リファレンス (林 晴比古 実用マスターシリーズ)』p.227です.iPad版でも,見ることができました.

文字にしておくと,callocの関数プロトタイプは「void *calloc(size_t n, size_t size)」となっていて*1,その説明が「sizeバイト×n個のメモリ確保」なのです.

このように,「×」の左と右にどんな数量を書くのかについては,

2番目の考え方は,倍概念と呼ばれます.さらに分類ができるのですが,本日は簡単のため,「かけられる数と答え(積)の単位を同じにする」と「かける数の単位は,場面(問題文など)をもとにして付けるが,計算において無視される」という2個1組に限定します.

ダブスタ考

に合致します.あらこんなところにも,という思いです.

callocの2つの引数の書き方,他の文書ではどうなっているか,見ていきますか.まずは日本語で…manpageかな.

void *calloc(size_t nmemb, size_t size);

calloc() は size バイトの要素 nmemb 個からなる配列にメモリを割り当て、割り当てられたメモリに対するポインタを返す。

Man page of MALLOC

かけ算の式にはなっていませんが,関数の宣言(書式)ではnmembが先,sizeが後のところ,説明では逆順になっています.

次は,英語のmanpageに当たりましょう.wikipedia:en:Man_pageを経由して,良さそうな情報を見つけました*2

void *calloc(size_t nmemb, size_t size);

The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory.

calloc(3): allocate/free dynamic memory - Linux man page

一つ前で引用した日本語は,これの逐語訳と言っていいでしょう.そして英文では,宣言と説明とで,nmembsizeの出現順が一致しています.

あとは,英語の書籍,C Programming Language (Prentice Hall Software)(K&R)です.

void *calloc(size_t n, size_t size)

returns a pointer to enough space for an array of n objects of the specified size, or NULL if the request cannot be satisfied. The storage is initialized to zero.

(p.167)

void *calloc(size_t nobj, size_t size)

calloc returns a pointer to space for an array of nobj objects, each of size size, or NULL if the request cannot be satisfied. The space is initialized to zero bytes.

(p.252)

引数の名前が,変わってしまっていますが,ここでも,宣言と説明とで,2つの引数の出現順は合っています.

これらは,乗法の構造を日本語・英語で表すとき,日本語は被乗数先書(何のいくつ分,「size バイトの要素 nmemb 個からなる配列」),英語は乗数先書(a times b, "an array of nmemb elements of size bytes")が自然であるという,状況証拠の一つとなるように思います.

補足その1

小学校で学習するかけ算の範囲でですが,日本と欧米との比較が,403 Forbiddenにてなされています.

補足その2

C言語と「かけ算の順序」については,過去に検討しています.

「1つ分の大きさ」×「いくつ分」で書いていないのは,まず,2次元配列の要素数で,例えばint b[2][3];と宣言したとき,配列変数bがint型のオブジェクトを全部でいくつ持つかというと,b[0][0], b[0][1], b[0][2], b[1][0], b[1][1], b[1][2]という順でメモリに確保されるので,3×2が自然であり,2×3と書くのは「現れる順に数字をかけた」ことを意味します.

自分の「×」の使い方 - わさっき

おととし12月に書いているのですが,再再考します.

int b;
int b_2[2];
int b_2_3[2][3];
int b_3_2[3][2];
int b_2_3_4_5_6_7[2][3][4][5][6][7];

と書いてみたとき,それぞれのサイズは,

  • sizeof(b)は,sizeof(int)
  • sizeof(b_2)は,sizeof(int)×2
  • sizeof(b_2_3)は,sizeof(int)×2×3
  • sizeof(b_3_2)は,sizeof(int)×3×2
  • sizeof(b_2_3_4_5_6_7)は,sizeof(int)×2×3×4×5×6×7

と表せます.配列の次元は,伸縮自在だからです.また,プログラムコードとの対応づけという点からも,このほうが自然に思えます.

sizeof(b_2)を乗数先書の2×sizeof(int)で表したり,sizeof(b_2_3_4_5_6_7)を「1あたり量×いくつ分」を繰り返し適用してsizeof(int)×7×6×5×4×3×2と表したりするのは,かけた手間に対して効果(人への伝わりやすさ)がほとんどありません*3.試験でsizeof(int)×2×3×4×5×6×7かsizeof(int)×7×6×5×4×3×2のどちらが正しいか,なんて問う意義も認められませんし,sizeof(int)×7×6×5×4×3×2と表すのが良いと考える学生を見つけるのからして困難です.

callocについては,仕様が決まっているので,縮めたり伸ばしたりすることを考える必要がありません.その分,かけ算の式でメモリサイズを表すとき,2つのファクターの意味,そしてそれぞれの違いを,より強く,意識せざるを得ないのです.

補足その3

もし,学生が「sizeバイト×n個のメモリ確保」に基づき

double *p = (double *)calloc(sizeof(double), 10);

と,第1引数に「一つ分の大きさ」,第2引数に「いくつ分」を書いていたら,指導すべきでしょうか.

やはり,指導すべきでしょう.これはかけ算(乗法の構造)というよりは,引数の対応づけが合っていないので,不適切だと考えます.

「動くからこれでいい」から「一つ一つの意味を点検しよう」という発想の類例として,最大値の求め方を改変したコードを取り上げたいと思います.配列aから最大値を求めるため,

max = a[0];
len = sizeof(a) / sizeof(a[0]);
for (i = 0; i < len; i++)
  if (max <= a[i])
    max = a[i]

と書く学生がいたとき,「i = 0」「max <= a[i]」は見直させたいところです.

補足その4

callocを含む,行列構造体の初期化プログラムコードを,第14回授業(1月30日)で取り上げました.演習室で実施し,ファイルを読み出して値を入れるという関数を打ち込ませました.時間の都合で,callocを含むコードは打ち込ませず,かわりにプログラム解説の中で説明しました.

月曜授業は,祝日や,大学祭の授業休止日があって,正規では15回の回数がとれず,翌31日に第15回授業を実施しました.

試験直前だったので,出題範囲を伝えました.その際,ライブラリ関数の中でmallocは勉強しておくことを指示し,callocはその対象としませんでした.

それで試験ですが,mallocを3つ記述し,row行column列で各成分はdouble型となる,行列構造体のコードを出題しました.

(減点なしの)正解率は0%でもいいやと思いながら,2番目のmallocで何を確保するのかを問題にしたところ,何人かが,こちらの意図を十分に汲んだ良解答を書いてくれたので,マルをつけました.

(最終更新日時:Sun Feb 19 06:27:54 2012ごろ.2つの引数を塗り分けました.)

*1:セミコロンがないのは,これが“関数プロトタイプ”だからです.同書のpp.123-124で,そのことを確認できます.セミコロンがついたら「関数原型(関数プロトタイプ)の宣言」です.JIS X 3010:2003では,「6.7.5.3 関数宣言子(関数原型を含む)」(p.89)という項目があり,「関数定義の一部でない関数宣言子」「関数定義の一部である関数宣言子」という表記も見られます.関数定義の中に関数宣言子が含まれていること,そして関数プロトタイプ宣言は,関数定義とは別の,関数プロトタイプの表明の仕方(コンパイラに伝える方法)であること,と考えればいいようです.

*2:知り得た中で最古は,http://man.cat-v.org/unix_8th/3/mallocです.「Unix 1st」「Unix 6th」の中には,callocは見当たりませんでした.

*3:ないわけでもなく,前年度まで教科書としていた『C言語によるプログラミング―スーパーリファレンス編』を開くと,p.203に「int m[2][3][4][5][6][7];」と配列を宣言し,「この配列は、3×4×5×6×7の配列が2つ並んだもの(つまり、m[0], m[1])と考えます」とあります.

リンク元