このページの解説は C99 をベースとしています。
以下は目次です。
これまでに、記憶域期間の種類として、自動記憶域期間📘と静的記憶域期間📘の2つが登場しました。記憶域期間とは、あるオブジェクト📘がいつからいつまでメモリ上に存在しているかのルールです。これは言い換えると、そのオブジェクトのメモリアドレス📘が有効である期間を表しているとも言えます。
あるオブジェクトの記憶域期間の範囲外に、そのオブジェクトへアクセスした場合は未定義の動作📘です。このことは当然、ポインタを経由した場合にもいえることです。すでに記憶域期間を終えているオブジェクトを、ポインタを経由してアクセスした場合も未定義の動作📘です。
この章では、3つ目の記憶域期間として、動的記憶域期間📘 (dynamic storage duration) (あるいは割付け記憶域期間 (allocated storage duration))を取り上げます。
規格上は「allocated storage duration」なので、訳語としては「動的」ではなく「割付け」の方が適切です(C言語の JIS規格文書でもそうなっています)。しかし、静的記憶域期間という用語との対比として「動的 (dynamic)」を使う機会が多いことと、C++ では「dynamic storage duration」となっていることから、当サイトでは動的記憶域期間の方で統一しています。
動的記憶域期間を持つオブジェクトは、任意のタイミングでメモリ上に置かれ、任意のタイミングで削除できます。たとえば、ある関数内でメモリ上に置き、別の関数内で消すといった、非常に自由度が高い制御が行える反面、慎重なプログラミングが要求されます。また、ポインタを使うことが必須になるので、ポインタの正しい理解が必要です。
オブジェクトを記憶しておくとき、メモリをどのように使うかという観点でも、記憶域期間による違いが見られます。ただし、以下の説明はあくまで、「多くの環境で」の話であるということに注意してください。規格上は、記憶域期間ごとのメモリの使い方に関して、具体的な規定はありません。
自動記憶域期間を持つオブジェクトは、メモリ内のスタック領域 (stack area) に配置されます。スタック領域は、メモリの中の比較的狭い範囲に固定的に用意されており、ここを使いまわすように利用されます。
「使いまわす」という部分がポイントで、ある関数の中で定義されたローカル変数のメモリアドレスと、異なる関数の中で定義されたローカル変数のメモリアドレスは一致する可能性があります。同時に必要になることはないのですから、メモリアドレスが一致してしまっても何ら問題がありません。また、同じ関数の同じローカル変数であっても、関数を呼び出すたびに、異なるメモリアドレスに配置される可能性もあります。
静的記憶域期間を持つオブジェクトは、メモリ内の専用の領域(静的領域 (static area) やグローバル領域 (global area))に配置されます。静的記憶域期間を持つオブジェクトは、プログラムの実行が開始されたときから、実行が終了するまで消えることがないので、メモリアドレスはつねに固定されています。
静的領域は、ビルドを行って実行ファイルを生成する過程の中で、必要な大きさが確定できるため、無駄な領域を取らないように割り当てられます。
動的記憶域期間を持つオブジェクトは、メモリ内のヒープ領域📘 (heap area) に配置されます。
ヒープ領域を使うためには、OS に「これだけの大きさの領域が欲しい」という要求を出さなくてはなりません。OS は要求に応じて必要な領域を確保し、そのメモリアドレスを返してくれます。また、その領域を使い終えて必要なくなったときには、OS に「この領域はもういらない」と伝えます。すると OS は、その領域を誰にも使われていない状態に戻します。
このように、プログラムの実行中にメモリを確保することを、動的メモリ割り当て(ダイナミックメモリアロケーション)📘 (dynamic memory allocation) と呼びます。また、使い終わったときにメモリを返却する過程を、メモリを解放(デアロケーション)📘 (deallocation、free、release) すると表現します。
では実際に、動的メモリ割り当てを行い、動的記憶域期間を持つオブジェクトを作ることを体験してみましょう。まずは、malloc関数を紹介します。malloc関数は、<stdlib.h> に次のように宣言されています。
void* malloc(size_t size);
malloc関数は、動的にメモリ領域を割り当て、そのメモリアドレスを返す関数です。確保されたメモリ領域には、不定値を持ったオブジェクトが置かれた状態です。いつものように、きちんと正しい値を入れてから使うようにしてください。このようにして確保されたオブジェクトは、動的記憶域期間を持ちます。
引数には、確保したい領域の大きさを指定します。少し感覚的にずれを感じる人もいるかもしれませんが、ここで指定するのはオブジェクトの「型」のような情報ではなく、オブジェクトを置くために必要十分な「大きさ」です。そのため、たとえば double型のオブジェクトを置くための領域が欲しいのなら、「sizeof(double)」を渡します。
【上級】sizeof を使っている限りはあり得ないことですが、引数に 0 を指定した場合の動作は処理系定義です。[1]
戻り値は、確保されたメモリ領域の先頭のメモリアドレスが返されます。もしなんらかの要因で(普通は、メモリ不足です)確保に失敗した場合には、ヌルポインタが返されます。
確保するオブジェクトの型は事実上何でもありなので、戻り値は voidポインタ(第34章)で返されます。これでは間接参照📘できないので、適切な型のポインタ変数で受け取るようにします。
int* p1 = malloc(sizeof(int)); // int 1個分
int* p2 = malloc(sizeof(int) * 100); // int 100個分
char* p3 = malloc(sizeof(char) * 101); // 100文字分
malloc関数の実引数は、確保するメモリ領域の大きさなので、単独のオブジェクトのための領域なら sizeof が返す値をそのまま渡せばよいです。配列の場合は要素数を掛け合わせて合計の大きさを渡します。
文字列を確保する場合には特に注意が必要です。末尾のヌル文字(‘\0’) も格納できるだけの領域がなければなりませんから、必ず 1文字分多く確保することを忘れないようにしてください。
malloc関数は失敗する可能性があることを考慮しなければなりません。動的メモリ割り当て📘に失敗するということは、ほとんどの場合、メモリが足りておらず、プログラムの実行を続けることができない状況のはずです。そのため、原則的には、プログラムを正しく終わらせるようにします。main関数の中であれば return文で終了できますが、そうでないなら、exit関数などの終了関数を使うことになります(第30章)。
int* array = malloc(sizeof(int) * 10000);
if (array == NULL) {
(EXIT_FAILURE);
exit}
プログラムを終了させてしまっていいのなら、malloc関数の失敗を確認する必要もなくて、エラーを放置してもいいのではと思うかもしれません。しかし、失敗を確認しなかったら、その先にある正常時のコードを実行していくので、おそらくどこかでヌルポインタを経由した間接参照をおこなうコードを実行します。これは未定義の動作📘ですから、「何が起こるか分かりません」。exit関数は「何が起きているか分かったうえでの終了」なので、まったく意味が違います。いかなる場面でも、未定義の動作は引き起こしてはなりません。
malloc関数の失敗を調べるにしても、malloc関数を呼ぶたびにチェックするのは面倒なので、独自の関数を用意することもあります。たとえば、次のような関数を用意し、つねにこれを使うようにしていれば、後から失敗時の対応の方針を変えることも容易です。
void* xmalloc(size_t size)
{
void* p = malloc(size);
if (p == NULL) {
(EXIT_FAILURE);
exit}
return p;
}
さて、動的に確保されたメモリ領域は、使い終わったら解放しなければならないのでした。解放には、free関数を使います。
void free(void* ptr);
引数には、malloc関数が返したポインタを渡します。動的に確保した領域以外を指すポインタを渡してはいけません。ただし、ヌルポインタを渡した場合には、何も起こらないことが保証されています。
free関数で解放が行われると、オブジェクトは動的記憶域期間を外れます。記憶域期間の範囲外にオブジェクトへアクセスする行為は未定義の動作📘ですから、free関数で解放されたオブジェクトへのアクセスは行ってはなりません。
free関数で解放することを忘れても、OS がきちんと管理している多くの環境では、プログラム終了時に自動的に解放されます。ただし、C言語の立場としては、これは必ずそうであるとは言えません。プログラム内で解放することを勧めます。
さて、それでは実際のプログラムを見てみましょう。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* p = malloc(sizeof(int));
if (p == NULL) {
(EXIT_FAILURE);
exit}
*p = 123;
("%d\n", *p);
printf
(p);
free}
実行結果:
123
以下の点を確認してください。
プログラム例を示したものの、通常、int型変数 1個のために、動的メモリ割り当てを使うことはあり得ません。動的メモリ割り当ては、実行速度の面でも、メモリ使用量の面でも不利に働きます。動的メモリ割り当てを使う価値があるのは、プログラムを実行してからでないと、どれだけの大きさのメモリがあれば十分なのか分からない場合です。たとえば、標準入力からデータを受け取る際、そのデータ総数が何個あるか分かりません。こういう場合には、動的メモリ割り当てを行って、配列を動的に確保します。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// データ件数を受け取る
("データの件数を入力してください。");
putschar buf[40];
int data_num;
(buf, sizeof(buf), stdin);
fgets(buf, "%d", &data_num);
sscanf
// データ件数が 0件以下なら終了
if (data_num <= 0) {
return 0;
}
// 件数に合わせて、領域を確保する
int* data_array = malloc(sizeof(int) * data_num);
if (data_array == NULL) {
(EXIT_FAILURE);
exit}
// データを受け取る
for (int i = 0; i < data_num; ++i) {
("データを入力してください。");
puts(buf, sizeof(buf), stdin);
fgets(buf, "%d", &data_array[i]);
sscanf}
// 結果を出力
for (int i = 0; i < data_num; ++i) {
("%d: %d\n", i+1, data_array[i]);
printf}
(data_array);
free}
実行結果:
データの件数を入力してください。
5
データを入力してください。
35
データを入力してください。
-50
データを入力してください。
95
データを入力してください。
20
データを入力してください。
-45
1: 35
2: -50
3: 95
4: 20
5: -45
このプログラムは、入力されるデータの総数が一定ではありません。プログラム作成時点ではデータ数が不明な場合、静的に領域を用意することが難しいです。用意する領域が少な過ぎると、膨大な量の入力に対応できないことになってしまいます。逆に、膨大な量の入力に備えて巨大な配列を作ると、実際のデータ数が少なかった場合、ほとんどのメモリが無駄になってしまいます。
動的に確保された配列を使う場合、ポインタを通して扱うという事情から、動的な配列は、sizeof演算子を使って配列全体の大きさを知ることができない点に注意が必要です。
char static_array[100];
char* dynamic_array = malloc(sizeof(char) * 100);
("%zu\n", sizeof(static_array)); // 100
printf("%zu\n", sizeof(dynamic_array)); // 4 printf
ポインタ経由で扱う以上、sizeof演算子が返す値は、つねにポインタ変数の大きさにしかならない訳です。このため、配列の要素数を調べる SIZE_OF_ARRAYマクロのようなマクロを用意していても、動的な配列には使用できません。どうしても後から要素数を知る必要がある場合には、管理が煩雑になりますが、malloc関数を呼び出したついでに、別の変数に要素数を保存しておくしかありません。
配列の動的な確保に関しては、calloc関数を使う方法もあります。calloc関数も <stdlib.h> にあります。
void* calloc(size_t n, size_t size);
第1引数に要素数を、第2引数に要素1つ分の大きさを指定します。戻り値は、malloc関数と同様に、動的に確保されたメモリ領域のメモリアドレスが返されます。失敗した場合の戻り値はヌルポインタです。
【上級】引数size に 0 を指定した場合の動作は処理系定義です。[1]
malloc関数と異なり、確保された領域の全ビットが自動的に 0 で埋められます。整数型📘であれば 0 で初期化されていると考えて良いですが、他の型の場合は想定と異なる意味を持つかもしれません。たとえば、ポインタの場合、「全ビットが 0」という状態が、ヌルポインタを表すとは限りませんし、浮動小数点型の場合、「全ビットが 0」=「0.0」とはならないかもしれません。
なお、calloc関数で確保した領域も、free関数で解放します。
calloc関数は、malloc関数で確保して、memset関数で 0 を埋めるのと同じ結果を生みます。
大抵は気にする必要はありませんが、さまざまな要因によって、行っていることが異なることがあり、効率面においても差が開くことがあります。
calloc関数の使用例を挙げます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const int size = 10;
int* array = calloc(size, sizeof(int));
if (array == NULL) {
(EXIT_FAILURE);
exit}
for (int i = 0; i < size; ++i) {
("%d\n", array[i]);
printf}
(array);
free}
実行結果:
0
0
0
0
0
0
0
0
0
0
malloc関数、calloc関数、free関数を正しく使うには、いろいろと注意すべき点があります。慣れるまで大変なので、ここにまとめておきます。
malloc関数、calloc関数に関して、
free関数に関して、
これだけのことを考慮するのは大変です。労力を減らすために、解放のための関数形式マクロを定義しておく方法があります。
#define SAFE_FREE(ptr) if(ptr != NULL){free(ptr); ptr = NULL;}
free関数を直接呼び出す代わりに、このマクロを使って解放するようにします。
int* array = malloc(sizeof(int));
(array); SAFE_FREE
こうすると、解放後にポインタ変数が NULL で上書きされます。ちなみに、free関数にヌルポインタを渡しても何も起こらないので、ヌルポインタかどうかのチェックはなくても構いませんが、効率面を考えてチェックすることが多いです。
このようなマクロを使うことで、以下のような利点が生まれます。
ただし、同じメモリ領域を指し示すポインタが 2つ以上あるような状況を作ってしまっていると、これだけで確実とはいえません。
動的メモリ割り当てを使うと、注意しなければならないことが多くなり、バグを作ってしまう可能性が増えます。実際、プログラムのバグの原因として、動的メモリ割り当ての扱いを失敗しているケースは非常に多いです。
確保も解放もそれなり処理時間がかかるものなので、処理速度の面でも不利です。また、要求した大きさよりも多くのメモリを確保されることがあります(数十バイト程度の管理情報が付加されることがあるため)。ある程度まとまった大きさの確保であれば、管理情報の大きさは、相対的に小さくなり、無視できる程度になりますが、細かい領域を大量に確保するような使い方は避けた方が良いでしょう。
このような理由から、動的メモリ割り当ては、明確な必要性がない限りは避けるべきです。
realloc関数は、動的に確保した領域の大きさを、後から変更するものです。
realloc関数は、<stdlib.h> に次のように宣言されています。
void* realloc(void* ptr, size_t size);
第1引数に、malloc関数、calloc関数、realloc関数のような方法で動的に確保された既存の領域を指すポインタを指定します。ヌルポインタを指定することも可能で、この場合は malloc関数と同じ動作、つまり単純に新しい領域を確保する動作になります。
第2引数には、新しく作られる領域の大きさを指定します。既存の領域の大きさよりも、小さくても大きくても同じでも問題ありませんが、0 を指定することには注意が必要で、原則として避けるべきです。
標準規格のバージョンとともに何度も更新されてきた経緯がありますが、C23規格で未定義動作とされています。バージョンごとの違いについては、標準ライブラリのリファレンスを参照してください。
戻り値は、新しい領域が確保できた場合にはその領域を指すポインタが返されます。新しい領域が作られなかった場合にはヌルポインタが返されます。前者の場合、新しい領域が必要なくなった後に free関数で解放する必要があることを忘れないようにしましょう。
realloc関数は少々複雑なので、イメージをつかみづらいかもしれません。実際にどんなことをしているのか、具体的な実装例をお見せしましょう。
void* realloc(void* ptr, size_t size)
{
if (ptr == NULL) {
return malloc(size); // 第1引数が NULL の場合、malloc関数と同じ動作になる
}
if (size == 0) {
// 第2引数が 0 の場合は、処理系定義
// 戻り値については、NULL か、NULL でないポインタが返される
return ???;
}
char* p = malloc(size); // malloc関数を使って、新しい方の大きさの領域を確保する
if (p == NULL) {
return NULL; // malloc関数が失敗した場合、NULL を返す。元の領域はそのまま残る
}
(p, ptr, size); // 元の領域にあるデータを、新しく確保した領域にコピーする
memcpy(ptr); // 元の領域は解放する
free
return p; // 新しい領域の先頭メモリアドレスを返す
}
上記のサンプルプログラムは、realloc関数がどのように実装されているかの一例を示すものであって、必ずこの程度に実装されているということではありません。しかし、標準規格にしたがっていれば、行っている内容は同等のはずです。
まず、最初の if文では、第1引数がヌルポインタの場合の処理を行っています。前述したように、第1引数が ヌルポインタの場合、malloc関数と同じことを行います。
2つ目の if文では、第2引数が 0 の場合の処理を行っています。ここは処理系定義なのでどうなるか分かりません。
その後、新しい大きさの領域を動的に割り当てています。これは当然、もともと確保されていた領域とは別のメモリアドレスに作られています。ここでメモリ不足などで失敗する可能性がありますが、その際には realloc関数自身の失敗としてヌルポインタを返して終了します。この場合には、もともと確保されていた領域には、何ら変化は起きていません。
もともと確保されていた領域にあった内容は、新しい領域に引き継がせたいので、内容をコピーする必要があります。memcpy関数でこれを行っています。
コピーを終えたら、もともと確保されていた領域は必要なくなるので、free関数を使って解放します。
処理の流れをまとめると、以下のとおりです。
したがって、古いメモリ領域にあったデータは、新しいメモリ領域にきちんと移動します。しかし、新しく確保しなおしているのですから、メモリアドレスは変わってしまいます。これは、古いメモリ領域にあったオブジェクトを指していたポインタを使って間接参照を行ってはならないということです。
また、realloc関数が成功したのであれば、古いメモリ領域は、realloc関数内で解放済みですから、自分で解放する必要はありません。
ではここで、実際に使用感を確かめておきましょう。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* array = NULL;
size_t size = 0;
while (1) {
char buf[40];
int num;
("次のデータを整数で入力してください。負数を入力すると終了します。");
puts(buf, sizeof(buf), stdin);
fgets(buf, "%d", &num);
sscanf
if (num < 0) {
break;
}
++;
size
// 領域を拡張する
int* tmp = realloc(array, sizeof(int)*size);
if (tmp == NULL) {
// realloc関数が失敗した場合、元の領域は解放されずに残されている
// 自分で free関数を呼び出して終了する
(array);
free(EXIT_FAILURE);
exit}
= tmp;
array = NULL; // 安全策。確保された領域を指すポインタを array だけに限定する
tmp
// 入力されたデータを格納
[size-1] = num;
array}
// 結果を出力
for (int i = 0; i < size; ++i) {
("%d\n", array[i]);
printf}
(array);
free}
実行結果:
次のデータを整数で入力してください。負数を入力すると終了します。
3
次のデータを整数で入力してください。負数を入力すると終了します。
5
次のデータを整数で入力してください。負数を入力すると終了します。
7
次のデータを整数で入力してください。負数を入力すると終了します。
10
次のデータを整数で入力してください。負数を入力すると終了します。
-1
3
5
7
10
標準入力から受け取ったデータを出力するだけのプログラムです。
realloc関数を使うときには、第1引数に指定するポインタ変数と、戻り値を受け取るポインタ変数を、別々の変数にするべきです。同じ変数を使って、以下のように書いたとします。
= realloc(array, sizeof(int)*size); array
こうしてしまうと、realloc関数が失敗した場合にヌルポインタが返されますから、ポインタ変数 array にはヌルポインタが代入されます。しかし、もともと array が指していたメモリ領域はまだ解放されていません。realloc関数が失敗した場合、realloc関数内の free関数は実行されないためです(前述の実装例を確認してください)。
ポインタ変数 array がもともと持っていたメモリアドレスの情報を上書きして失ってしまったため、free関数を呼ぶことができず(free関数の実引数に何を渡せばいいのか?)、メモリ領域を解放する術がなくなってしまうのです。
そのため、realloc関数の戻り値は、第1引数に渡すポインタ変数とは異なるポインタ変数で受け取り、realloc関数が成功したときに、あらためて array へ代入しなおすようにします。
サンプルプログラム内で「安全策」というコメントがあります。realloc関数の戻り値を受け取るために使ったポインタ変数(tmp) は、本来代入すべきポインタ変数(array) に値をコピーした後には不要になります。使わなければよいだけのことではありますが、たとえば間違って free(tmp);
と free(array);
を両方書いてしまうようなミスを犯すと、解放済みのメモリ領域を再度解放するという未定義の動作📘になってしまいます。
そこで、もう使わなくなったポインタ変数にはヌルポインタを入れておくという手を取っています。これで間違って free(tmp);
を書いたとしても、何も起こりませんから安全です。
ところで、先ほどのサンプルプログラムのように、データが 1件増えるたびに、realloc関数を呼ぶような実装は、非常に効率が悪い可能性があります。新しいメモリ領域を割り当てることや、そこへデータをコピーすることは、実行速度への影響が大きいためです。
そこでたとえば、領域が足りなくなったら、領域を倍々に増やしていくなどして、新しい領域の割り当てやコピーを、できるだけ一括でおこなうようにする効率改善策が取られることがあります。
問題① 次のプログラムの誤りを指摘してください(メモリ不足への対策を行っていない点は無視してください)
#include <stdio.h>
#include <stdlib.h>
#define NAME_LEN 64 // 名前の最大長
#define PHONE_NUMBER_LEN 16 // 電話番号の最大長
// 個人情報
typedef struct {
char name[NAME_LEN]; // 名前
char phone_number[PHONE_NUMBER_LEN]; // 電話番号
} PersonalInfo;
int main(void)
{
// データ件数を受け取る
("データの件数を入力してください。");
putschar buf[40];
int data_num;
(buf, sizeof(buf), stdin);
fgets(buf, "%d", &data_num);
sscanf
// データ件数が 0件以下なら終了
if (data_num <= 0) {
return 0;
}
// 件数に合わせて、領域を確保する
* info_array = malloc(sizeof(PersonalInfo) * data_num);
PersonalInfoif (info_array == NULL) {
(EXIT_FAILURE);
exit}
// データを受け取る
for (int i = 0; i < data_num; ++i) {
("名前を入力してください。");
puts(buf, sizeof(buf), stdin);
fgets(buf, "%s", info_array[i].name);
sscanf
("電話番号を入力してください。");
puts(buf, sizeof(buf), stdin);
fgets(buf, "%s", info_array[i].phone_number);
sscanf}
// 特に手を加えないので、領域を解放する
(info_array);
free
// 結果を出力
for (int i = 0; i < data_num; ++i) {
("%s: %s\n", info_array[i].name, info_array[i].phone_number);
printf}
}
問題② 次のプログラムの誤りを指摘してください(メモリ不足への対策を行っていない点は無視してください)
#include <stdio.h>
#include <stdlib.h>
void set_sequential_number(int* array, size_t size);
void print_array(int* array, size_t size);
int main(void)
{
int* values;
size_t size;
= 50;
size = malloc(sizeof(int) * size);
values (values, size);
set_sequential_number(values, size);
print_array
= 100;
size = calloc(size, sizeof(int));
values (values, size);
set_sequential_number(values, size);
print_array
(values);
free}
/*
配列に連番をセットする。
引数:
array: 対象配列のメモリアドレス。
size: 対象配列の要素数。
*/
void set_sequential_number(int* array, size_t size)
{
for (size_t i = 0; i < size; ++i) {
[i] = i;
array}
}
/*
配列の中身を標準出力へ出力する。
引数:
array: 対象配列のメモリアドレス。
size: 対象配列の要素数。
*/
void print_array(int* array, size_t size)
{
for (size_t i = 0; i < size; ++i) {
("%d\n", array[i]);
printf}
}
問題③ 標準入力から、int型に収まる整数値が繰り返し入力されるとして、すべての入力を受け取った後、受け取ったすべての数値を標準出力へ出力するプログラムを作成してください。負数が入力されると終了します(これは realloc関数のところのサンプルプログラムと同様です)。
ただし、メモリ領域が倍々の大きさで確保されるようにしてください。最初の大きさは任意で決めて構いません。
問題④ 標準入力から、long型に収まる整数値が繰り返し入力されるとして、すべての入力を受け取った後、入力された数値の平均値を出力するプログラムを書いてください。入力件数は不明ですが、最大でも 1000件とし、負数が入力されると終了します。
ただし、必要なメモリ領域はプログラムの開始直後にまとめて確保し、最後に realloc関数を使って、使われなかった領域を切り詰める方法で実装してください。
問題⑤ realloc関数を次の条件でラップした関数を作成してください。
問題⑥ 次のような関数を作成しました。
char* str_duplicate(const char* s)
{
char* dup = malloc(strlen(s) + 1);
(dup, s);
strcpy
return dup;
}
この関数は何をしているか説明してください。また、この関数を実際に使用したプログラムを作成してください。
{
の直後と、}
の直前に空白を入れない)()
の前後の空白の空け方)(
の直後、)
の直前に空白を入れない)return 0;
を削除(C言語編全体でのコードの統一)
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
![]() |
管理者情報 | プライバシーポリシー |