このページをシェアする

XをY分で学ぶ

ただし X=C

え、C? あぁ、未だにモダンで高パフォーマンスを実現できるあの言語のことだな。

Cはほとんどのプログラマが最低水準言語として使われているが、その特徴は実行速度の速さだけ ではないのだ。CはPythonなどの高水準言語とは異なり、メモリの自動管理機能がなく、 プログラマーの手で管理する必要があり、これが初学者を苦しめる要素となるが、うまく使えば、 ロボットなどで実行速度やメモリの使用率などを大幅に最適化できる。

コンパイラフラグについて

gccやclangなどのコンパイラではデフォルトでデバッグに有益なエラーや警告を表示しない 設定になっています。なので、それらのエラーを詳細に、厳しく表示させるフラグと共に 実行することをおすすめします。下記はそのフラグの例です:

-Wall -Wextra -Werror -O2 -std=c99 -pedantic

このようなフラグの詳細については、オンライン検索にかけるか、 コンパイラのドキュメンテーションを読んでください。(Linuxならman 1 gcc等)

// 行コメントは//で始まる (C99より前のC標準では使えない)

// Cに限ったことではないが、ソースコードで日本語コメントを書くときにはファイルを
// UTF-8で保存することをおすすめします。なぜならgccなど特定のコンパイラでは
// 文字コード変換の影響で意図しないコメントアウトが引き起こされる可能性があります。

// 例:
// forループで似たコードの繰り返しを解消することが可能
// このコメントを消すと何故か動かない
for (int i = 0; i < 100; i++) {
    printf("%d\n", i);
}
// 解説:shift-jisで「能」は 94 5c で、標準ASCIIでは 5c は"\"でLinux gccでは
// 次の行もコメントアウトされる仕様で、この例ではforループの最初の定義が
// コメントアウトされエラーとなります。

/*
複数行コメント、C89標準でも使える。
*/

/*
複数行コメントはネストできないので/*注意*/ // コメントはここで終わり、
*/ // ここのコメント終了は扱われない。

// 定数・マクロ:#define <定数名(英数字のみ)>
// 定数はすべて大文字で定義することをおすすめします。
#define DAYS_IN_YEAR 365

// 列挙体も定数を定義する方法の一つです。
// すべてのコード行は半角英数字で書き、セミコロン「;」で終わる必要があります。
enum days {SUN, MON, TUE, WED, THU, FRI, SAT};
// SUNは0、MONは1、TUEは2、などと続く。

// 列挙体の値は別の値にできますが、数字が大きくなるようにする必要があります。
enum days {SUN = 1, MON, TUE, WED = 99, THU, FRI, SAT};
// MONは自動的に2、TUEは3、と続き、WEDで99、THUは100、FRIは101、などと続く。

// #include でヘッダーファイルをインポートできる。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// <アングルブラケット>で囲まれたファイル名はヘッダーファイルをシステムライブラリから
// 探すことをコンパイラに伝え、自分で書いたヘッダーファイルを使うときには ”引用符” で
//そのファイルのパスを囲みます。
#include "my_header.h"               // ローカルファイル
#include "../my_lib/my_lib_header.h" // 相対パス

// 予め関数を .h (ヘッダー)ファイルで宣言するか、
// .c (ソースコード)ファイルの上方に書いて宣言してください。
void function_1();
int function_2(void);

// 関数を使用する前に、最低でも、関数プロトタイプを宣言しなければなりません。
// プロトタイプは関数定義の前に書くのが一般的です。
int add_two_ints(int x1, int x2); // 関数プロトタイプ
// 上記の書き方でも問題ありませんが(引数の連番)、引数にはコード保守を
// 容易にするためになるべくちゃんとした名前をつけてあげましょう。

// 関数プロトタイプはその関数を使う前に定義を書いておけば必要ありません。
// しかし、関数プロトタイプをヘッダーファイルに記述し、ソースコードの上方に#includeを
// 使ってインポートすれば、コンパイラにまだ定義されていない関数を呼び出すことを防ぎ、
// ヘッダーファイルにどんな関数が定義されるのかが分かるのでプログラムの保守性も上がります。

// プログラムが最初に実行する関数はエントリーポイントといい、Cではmain()関数となります。
// 返り値はどんな型でも良いですが、Windowsなどの大抵のOSはエラーコードを検知・処理するために
// 関数はint型(整数型)を返すことが定められています。
int main(void) {
  // プログラムはここへ
}

// コマンドライン引数はプログラムの挙動やオプションを実行時に設定することができます。
// argcは引数の数を表し、プログラム名もカウントされるので常に1以上の値が入ります。
// argvは引数文字列の配列を表し、プログラム名含むすべての引数が入るます。
// argv[0]はプログラム名を、argv[1]は最初の引数などです。
int main (int argc, char** argv)
{
  // コンソールに文字などを表示するときにはprintf関数を使います。
  // printfは”print format”のことで、書式に沿って値を表示させます。
  // %dには整数が入り、\nは新しい行("n"ew line)へ移動します。
  printf("%d\n", 0); // => 0が表示される

  // scanf関数はコンソールからユーザの入力を受け付けます。
  // 変数の前の'&'記号はメモリ上の変数の住所(address)を求める一種の演算子です。
  // この例では整数型の値をinput変数の住所に値を代入します。
  int input;
  scanf("%d", &input);

  ///////////////////////////////////////
  // 型
  ///////////////////////////////////////

  // C99標準の互換性がないコンパイラでは、そのスコープで使用するすべての変数は
  // スコープの一番上に宣言する必要があります。C99標準互換のコンパイラは、使用する前なら
  // スコープ内のどこでも宣言可能です。このチュートリアルでは、C99標準に統一して書いていきます。

  // int型は大抵の場合整数を4バイトのメモリで格納しますが、古いCPUでは2バイトで格納します。
  // sizeof演算子を使えば、その型が何バイト使うか確認できます。
  int x_int = 0;

  // short型は2バイトで整数を格納。
  short x_short = 0;

  // char型は大抵のプロセッサーでは、最小のサイズで、
  // 1バイトのサイズで整数またはASCII文字一つを格納できます。
  // この型のサイズはプロセッサーによって異なり、2バイト以上の物もあります。
  // (例:TIからリリースされたTMS320は2バイトで格納される。)
  char x_char = 0;
  char y_char = 'y'; // ASCII文字リテラルは''で囲まれる。

  // long型は4~8バイトで整数を格納します。long long型は常に8バイトであることが保証されます。
  long x_long = 0;
  long long x_long_long = 0;

  // float型は32ビットの単精度浮遊少数を格納します。
  float x_float = 0.0f; // 'f'はその数字リテラルが単精度浮遊少数であることを示します。

  // double型は64ビットの倍精度浮遊少数を格納します。
  double x_double = 0.0; // 実数のあとに何もつかない数字リテラルは倍精度浮遊少数として扱います。

  // 整数型はunsignedをつけることで0以上の正の数のみを格納させることができます。
  unsigned short ux_short;
  unsigned int ux_int;
  unsigned long long ux_long_long;

  // char型に格納されている文字はASCIIなどの文字コードに対応する整数でもあります。
  '0'; // => ASCIIで48を表す。
  'A'; // => ASCIIで65を表す。

  // sizeof(T)でその型のサイズをバイトで返す(Tには型名が入る。)
  // sizeof(obj)はその値の型のサイズを返す。(objには定数、変数、生の値が入る。)
  printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words)

  // もしsizeof演算子の引数が式だった場合、VLA(可変長配列、Variable Length Array)でない限り、
  // その式は評価されません。この場合の引数の値はコンパイル時定数である。
  int a = 1;
  // size_t型は変数などの型サイズを表す2バイト以上の非負の整数を格納します。
  size_t size = sizeof(a++); // a++ は評価されない。
  printf("sizeof(a++) = %zu where a = %d\n", size, a);
  // prints "sizeof(a++) = 4 where a = 1" (32ビット環境での場合)

  // 配列は定義時にサイズを決める必要があります。
  char my_char_array[20]; // この配列は 1 * 20 = 20 バイト使います
  int my_int_array[20]; // この配列は 4 * 20 = 80 バイト使います
  // (4バイト整数環境であると仮定した場合)

  // 次の定義では整数配列を20個の0で埋めた状態で初期値が与えられます。
  int my_array[20] = {0};
  // "{0}"は配列初期化子です。
  // 初期化子に含まれる要素以外の要素は、(もしあれば)すべて0に初期化されます:
  int my_array[5] = {1, 2};
  // 上記の定義ではmy_arrayは5つの要素があり、最初の2つ以外は0に初期化されています:
  // [1, 2, 0, 0, 0]
  // 配列定義のときに明示的に初期化を行えば、要素数を決める必要がなくなります:
  int my_array[] = {0};
  // サイズを指定しないで定義すると配列初期化子の要素数がそのまま自動的に決めまれます。
  // よって"{0}"で初期化した場合、配列のサイズは1となり、"[0]"が代入されます。
  // 実行時に配列の要素数を調べるには、配列のサイズを1つの要素のサイズで割れば良いのです。
  size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]);
  // 注意;この操作は配列ごとに、かつ関数に渡す前に実行することを勧めします。なぜなら
  // 関数に配列を渡すと、ポインター(メモリ上の場所を表す単なる整数)に変換され、
  // 関数内で同じ操作を行うと、間違った結果につながる恐れがあるからです。

  // 要素へアクセスするときは他の言語と同じようにできます。
  // 正しく言えば、Cに似た言語です。
  my_array[0]; // => 0

  // 配列は変更可能です。
  my_array[1] = 2;
  printf("%d\n", my_array[1]); // => 2

  // C99標準以降(C11では任意選択)では、可変長配列(VLA)が使用可能で、コンパイル時に
  // 定数による要素数指定をしなくても、変数などによる指定ができるようになります。
  printf("Enter the array size: "); // ユーザーに要素数を入力してもらう。
  int array_size;
  fscanf(stdin, "%d", &array_size);
  int var_length_array[array_size]; // VLAを宣言する。
  printf("sizeof array = %zu\n", sizeof var_length_array);

  // 例:
  // > Enter the array size: 10
  // > sizeof array = 40

  // 文字列はヌル文字(0x00, '\0')で終わる配列でもあります。
  // 文字列リテラルを使用する場合はコンパイラが末尾に塗る文字を追加するので明示的に
  // 入れなくても良いです。
  char a_string[20] = "This is a string";
  printf("%s\n", a_string); // %s フォーマットで文字列を表示

  printf("%d\n", a_string[16]); // => 0
  // 例, 17番目のバイトは0 (18, 19, 20番目も同様)

  // シングルクォーテーションで囲まれた文字は文字リテラルです。これはchar型*ではなく*、
  // int型です。(これには歴史的背景があります。)
  int cha = 'a'; // OK
  char chb = 'a'; // これもOK (intからcharへの暗黙的型変換)

  // 多次元配列:
  int multi_array[2][5] = {
    {1, 2, 3, 4, 5},
    {6, 7, 8, 9, 0}
  };
  // 要素の取得:
  int array_int = multi_array[0][2]; // => 3

  ///////////////////////////////////////
  // 演算子
  ///////////////////////////////////////

  // 複数の同一型変数の略記法:
  int i1 = 1, i2 = 2;
  float f1 = 1.0, f2 = 2.0;

  int b, c;
  b = c = 0;

  // 四則演算は直感的にかけます:
  i1 + i2; // => 3
  i2 - i1; // => 1
  i2 * i1; // => 2
  i1 / i2; // => 0 (0.5だが、0に繰り下げられている)

  // 結果を少数にするにはどちらか一方の変数をfloat型へキャスティングする必要がある。
  (float)i1 / i2; // => 0.5f
  i1 / (double)i2; // => 0.5 // double型でも同様の操作ができる
  f1 / f2; // => 0.5, プラスマイナス計算機イプシロン(その型が表せる最小の少数)

  // 浮動小数点数はIEEE 754の仕様で定義されているので、コンピューターは正確な
  // 数をメモリ上で保存できない。よって意図しない数になることがある。例えば、0.1は
  // 0.099999999999、0.3は0.300000000001として保存されているかもしれません。
  (0.1 + 0.1 + 0.1) != 0.3; // => 1 (真)
  // なのでこれは上記の理由でこの真偽式は真になりません。
  1 + (1e123 - 1e123) != (1 + 1e123) - 1e123; // => 1 (真)
  // こちらは科学的表記法です : 1e123 = 1*10^123

  // ほとんどのシステムはIEEE 754に基づいて浮動小数点数を定義していることを
  // 知っておくことが重要になってきます。科学演算で多用されるPythonでも最終的に
  // IEEE 754を使うCを呼び出すことになります。この注意書きはCの浮動小数点数の
  // 仕様が悪く使うべきではないということをほのめかすのではなく、こういった誤差
  // (イプシロン)を考慮した上で比較するというのを頭に入れておくために書かれました。

  // 剰余演算もありますが、負の値を計算するときには注意してください:
  11 % 3;    // => 2 (11 = 2 + 3*x (x=3))
  (-11) % 3; // => -2 (-11 = -2 + 3*x (x=-3))
  11 % (-3); // => 直感に反し被除数と同じ符号になる、2 (11 = 2 + (-3)*x (x=-3))

  // 比較演算は親しみがあるかもしれませんが、Cには真偽型がなく、
  // 代わりに整数型が使われます。(C99以降は _Bool型がstdbool.hで
  // 提供されました。) 0は偽を表し、それ以外はすべて真として扱います。
  // 比較演算を使用する際は必ず0か1を返します。
  3 == 2; // => 0 (偽) 等しい
  3 != 2; // => 1 (真) 等しくない
  3 > 2;  // => 1       より大きい
  3 < 2;  // => 0       より小さい
  2 <= 2; // => 1       以下
  2 >= 2; // => 1       以上

  // CはPythonではないので、演算子の連鎖はできません。
  // 下記の例では問題なくコンパイルしますが、`0 < a < 2`は`(0 < a) < 2`になり、
  // `(0 < a)`の結果が真でも偽でも結局`0 < 2`または`1 < 2`となるので常に真となります。
  int between_0_and_2 = 0 < a < 2;
  // 代わりにこう書きます:
  int between_0_and_2 = 0 < a && a < 2;

  // 整数に対する論理演算子:
  !3; // => 0 (否定)
  !0; // => 1
  1 && 1; // => 1 (論理積)
  0 && 1; // => 0
  0 || 1; // => 1 (論理和)
  0 || 0; // => 0

  // 条件付き三元式 ( ? : )
  int e = 5;
  int f = 10;
  int z;
  z = (e > f) ? e : f; // => 10 "もし(e > f)が真ならばeを、偽ならばfを返す。"

  // 加算・減算演算子:
  int j = 0;
  int s = j++; // jを返してからjを1増やす (s = 0, j = 1)
  s = ++j; // jを1増やしてからjを返す (s = 2, j = 2)
  // 減算演算子 j-- と --j でも同様

  // ビット演算子
  // 整数などのデータは0と1の2進数で表されておりそれぞれをビットといいます。
  // これらの演算子は各ビットに論理演算を適用します。
  ~0x0F; // => 0xFFFFFFF0 (ビット単位NOT、補数、32ビット16進数整数での例)
  0x0F & 0xF0; // => 0x00 (ビット単位AND)
  0x0F | 0xF0; // => 0xFF (ビット単位OR)
  0x04 ^ 0x0F; // => 0x0B (ビット単位XOR)
  0x01 << 1; // => 0x02 (算術左シフト (1ビット幅))
  0x02 >> 1; // => 0x01 (算術右シフト (1ビット幅))

  // 正負のついた整数に対するビットシフトには注意してください - これらの操作は未定義です:
  // - 符号ビットへのビットシフト (int a = 1 << 31)
  // - 負の整数を左シフトする (int a = -1 << 2)
  // - 型のビットサイズ以上の幅でシフト:
  //   int a = 1 << 32; // 32ビット幅の整数の場合では未定義の動作

  ///////////////////////////////////////
  // 制御構造
  ///////////////////////////////////////

  // 条件文
  if (0) {
    printf("I am never run\n");
  } else if (0) {
    printf("I am also never run\n");
  } else {
    printf("I print\n");
  }

  // whileループ文
  int ii = 0;
  while (ii < 10) { // 10以下の整数がこの条件を満たす
    printf("%d, ", ii++); // ii++ が値を使用してから1加算される。
  } // => "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "が出力される

  printf("\n");

  // do-whileループ文
  int kk = 0;
  do {
    printf("%d, ", kk);
  } while (++kk < 10); // ++kk が値を使用する*前*に1加算される.
  // => "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "が出力される

  printf("\n");

  // forループ文
  int jj;
  for (jj=0; jj < 10; jj++) {
    printf("%d, ", jj);
  } // => "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "が出力される

  printf("\n");

  // *****注*****:
  // ループ文、関数には最低でも一つの命令・文が必要になります:
  int i;
  for (i = 0; i <= 5; i++) {
    ; // セミコロン単体で何もしないという命令を作れる(ヌル命令)
  }
  // 別の表記法:
  for (i = 0; i <= 5; i++);

  // switch文(if文より高速)
  switch (a) {
  case 0: // caseレーベルにはint型や列挙型やchar型等の整数で表せるものに限定されます。
    printf("Hey, 'a' equals 0!\n");
    break; // ブレイク文がなければ後続のcaseレーベルも実行されてしまいます。
  case 1:
    printf("Huh, 'a' equals 1!\n");
    break;
    // break文がそのcaseレーベルになければ、break文があるレーベルまですべて実行されます。
  case 3:
  case 4:
    printf("Look at that.. 'a' is either 3, or 4\n");
    break;
  default:
    // 上記の条件がすべて合致しなければdefaultレーベル下の命令が実行されます。
    fputs("Error!\n", stderr);
    exit(-1);
    break;
  }

  // goto文
  typedef enum { false, true } bool;
  // C99より前のC標準ではブール値が標準で定義されていません。
  bool disaster = false;
  int i, j;
  for(i=0; i<100; ++i)
  for(j=0; j<100; ++j)
  {
    if((i + j) >= 150)
        disaster = true;
    if(disaster)
        goto error;  // 両方のforループから抜ける
  }
  error: // goto error;"で"error"レーベルまで「ジャンプ」します。
  printf("Error occurred at i = %d & j = %d.\n", i, j);
  /*
    この例の出所: https://ideone.com/GuPhd6
    "Error occurred at i = 51 & j = 99."が出力されます。
  */
  /*
    ほとんどの場合、goto文を使うのは、そのコードが何をするかわかっていない限り、
    良くないとされています。詳細は
    https://ja.wikipedia.org/wiki/スパゲッティプログラム#goto文の濫用
    を読んでください。
  */

  ///////////////////////////////////////
  // 型キャスティング(型変換)
  ///////////////////////////////////////

  // すべての値には型がありますが、これらは、互換性がある別の型にキャスティングすることができます。

  int x_hex = 0x01; // 16進数リテラルで変数を定義できます。
                    // 2進数リテラルにはコンパイラごとに差があります。
                    // (GCCではx_bin = 0b0010010110)

  // 型キャスティングを行うとその値を保持しようとします。
  printf("%d\n", x_hex); // => 1
  printf("%d\n", (short) x_hex); // => 1
  printf("%d\n", (char) x_hex); // => 1

  // キャスティング先の型のサイズより大きい値をキャストすると警告なしに値が丸められます。
  printf("%d\n", (unsigned char) 257); // => 1 (8ビット長のunsigned char型が保持できる最大値は255)

  // char, signed char, unsigned char型の最大値はそれぞれ、<limits.h>で提供される
  // CHAR_MAX, SCHAR_MAX, UCHAR_MAXマクロを使用できます。

  // 整数型と浮動小数点数型は双方向にキャスティング可能です。
  printf("%f\n", (double) 100); // %f はdouble型と
  printf("%f\n", (float)  100); // float型をフォーマットします。
  printf("%d\n", (char)100.0);

  ///////////////////////////////////////
  // ポインター
  ///////////////////////////////////////

  // ポインターはメモリ上のアドレスを保持する整数の変数であり、型と共に宣言・定義されます。
  // 変数から直接アドレスを取得できることができます。

  int x = 0;
  printf("%p\n", (void *)&x); // &を用いて変数のアドレスを取得します。
  // (%p は void *型の値をフォーマットします。)
  // => 結果: 変数が保持されているメモリーアドレスが表示される

  // ポインターは型名の直後にまたは変数名の直前に * を書いて宣言・定義します。
  int *px, not_a_pointer; // px はint型の値を指すポインター
  px = &x; // pxにxのアドレスを代入する。
  printf("%p\n", (void *)px); // => &xと同様の結果が出力されるはずです。
  printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer));
  // => 64ビット環境では"8, 4"が出力されます。

  // ポインターから指示しているメモリー領域の値を取得(ディレファレンス)するには
  // ポインター宣言と同じようにポインター名の前に * を書きます。
  printf("%d\n", *px); // => xの値である0を出力

  // この機能を用いて、ポインターが指示している値を変更することができます。
  // 加算演算子はディレファレンス演算子より優先順位が高いので数学同様ディレファレンス操作を
  // 丸括弧で括ります。
  (*px)++; // pxが指しているxの値を1加算する
  printf("%d\n", *px); // => 1
  printf("%d\n", x); // => 1

  // 配列は連続したメモリー領域を確保するのに有効です。
  int x_array[20]; // 長さ20の不可変長配列を宣言
  int xx;
  for (xx = 0; xx < 20; xx++) {
    x_array[xx] = 20 - xx;
  } // x_arrayの値を 20, 19, 18,... 2, 1 と一括初期化する。

  // int型の値を指し示すポインターを宣言し、x_arrayのアドレスで初期化する
  int* x_ptr = x_array;
  // x_ptrは整数20個の配列の最初の要素を指しています。
  // この場合配列は代入時に最初の要素へのポインターへ変換されます。
  // 関数に配列を渡す際にも暗黙的にポインターに変換されます。
  // 例外:`&`を配列に適用した場合、その配列のアドレスが返り、要素の型ではなく、
  // 配列型のポインターが使用されます:
  int arr[10];
  int (*ptr_to_arr)[10] = &arr; // &arr は `int *`型ではない!
  // これは「(10個の整数の)配列へのポインター」型です。
  // もう一つの例外には文字列リテラルをchar型配列に代入する場合:
  char otherarr[] = "foobarbazquirk";
  // または、`sizeof`, `alignof`演算子を使用した場合:
  int arraythethird[10];
  int *ptr = arraythethird; // equivalent with int *ptr = &arr[0];
  printf("%zu, %zu\n", sizeof(arraythethird), sizeof(ptr));
  // "40, 4" または "40, 8" が出力されます。

  // ポインター型の値を加算・減算するとその方に応じて操作できます。
  // この操作のことをポインター演算といいます。
  printf("%d\n", *(x_ptr + 1)); // => 19
  printf("%d\n", x_array[1]); // => 19

  // 標準ライブラリ関数の一つであるmallocを使えば連続したメモリ領域を動的に確保できます。
  // malloc関数は確保するバイト数を設定するsize_t型の引数が一つあります。
  // (確保するのは大抵の場合ヒープ領域に確保されますが、組み込みデバイスなどでは
  // 挙動が異なる場合があります。このことはC標準では説明されていません。)
  int *my_ptr = malloc(sizeof(*my_ptr) * 20);
  for (xx = 0; xx < 20; xx++) {
    *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
  } // メモリー領域を整数型配列として初期化する [20, 19, 18 ... 1]

  // mallocで確保されたメモリー領域へのデータの書き込みには注意してください。
  // 安全性を保証するには、確保すると同時にそのメモリー領域をすべて0で埋め尽くすcalloc関数を使用してください。
  int* my_other_ptr = calloc(20, sizeof(int));

  // Cには動的配列のサイズをその場で求める方法はほとんどなく、関数などに渡すときに要素数を記録する別の変数が
  // 必要になることがよくあります。詳細は次の関数についてのセクションを読んでください。
  size_t size = 10;
  int *my_arr = calloc(size, sizeof(int));
  // 要素を追加する
  size++;
  my_arr = realloc(my_arr, sizeof(int) * size); // realloc関数で配列のサイズを更新する。
  if (my_arr == NULL) {
    // mallocやreallocなどを使う際には領域確保に異常がない確認するために
    // ヌルチェックをすることをおすすめします。
    return
  }
  my_arr[10] = 5;

  // 確保されていないメモリー領域へアクセスは予測不可能な結果を招く可能性があります。
  printf("%d\n", *(my_ptr + 21)); // => who-knows-what? が出力される**かも**、クラッシュするかもしれない。

  // メモリー領域の使用を終えたら必ずfree関数を使ってその領域を解放しなければなりません。
  // 解放しなければ、プログラムが終了しても他のプログラムからそのメモリー領域を再利用できず、
  // システム全体で使用できる容量が減ってしまいます。このことをメモリーリークと呼びます。
  free(my_ptr); // my_ptrでポイントされてるメモリー領域を解放する。

  // 文字列はchar型の配列で表せますが、よく使用されるのは文字列の最初の文字を指すcharポインターです。
  // もし、単に文字列リテラルを使用するだけならば"const char*"を使い、変更不能にしておくことが推奨されています。
  // なぜならば、本来文字列リテラルのデータは変更すべきではないからです。
  // なので、" foo[0] = 'a' "といった操作はできません。
  const char *my_str = "This is my very own string literal";
  printf("%c\n", *my_str); // => 'T'

  // char型の配列で定義されている場合は別で、文字列リテラルで初期化できますが、
  // 各要素は変更可能です。例に:
  char foo[] = "foo";
  foo[0] = 'a'; // この操作は許されており、"aoo" に変更される。

  function_1();
} // main 関数の終わり

///////////////////////////////////////
// 関数
///////////////////////////////////////

// 関数定義の構文:
// <戻り値の型> <関数名>(<引数>)

int add_two_ints(int x1, int x2)
{
  return x1 + x2; // returnで値を返す。
}

/*
関数は値によって呼び出されます。関数が呼ばれると、引数として渡した値はコピーされ、
関数内で値を変更したとしても、渡した引数の値は変わりません。(配列はこれに従わない。)

関数内で引数の値を変更したい場合はポインターとして渡す必要があり、配列も渡すときに自動的にポインターになります。

例:即興文字列反転
*/

// void型関数は値を返さない
void str_reverse(char *str_in)
{
  char tmp;
  size_t ii = 0;
  size_t len = strlen(str_in); // `strlen()` はC標準ライブラリ関数です。
                               // NOTE: `strlen` で返される文字列の長さは終端文字の
                               //       ヌルバイト('\0')を含んでいない状態です。
  // C99標準以降では、ループ定義の中にループ制御変数が定義できます。
  // 例:`for (size_t ii = 0; ...`
  for (ii = 0; ii < len / 2; ii++) {
    tmp = str_in[ii];
    str_in[ii] = str_in[len - ii - 1]; // 後ろから ii 番目の要素 を 前から ii 番目の要素にする
    str_in[len - ii - 1] = tmp;
  }
}
//NOTE: string.h のヘッダーファイルを#includeしないとstrlen()関数が使用できません。

/*
char c[] = "This is a test.";
str_reverse(c);
printf("%s\n", c); // => ".tset a si sihT"
*/
/*
一つの変数を変更することができるように、
ポインター渡しで複数の変数を変更できます。
*/
void swapTwoNumbers(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
/*
int first = 10;
int second = 20;
printf("first: %d\nsecond: %d\n", first, second);
swapTwoNumbers(&first, &second);
printf("first: %d\nsecond: %d\n", first, second);
// 変数の値が交換される
*/

// 一度に複数の値を返す
// Cではreturn文を使って複数の値を返すことができません。一度に複数の値を返すには、
// 引数にそれぞれの戻り値を格納する変数へのポインターを設定しなければなりません。
int return_multiple( int *array_of_3, int *ret1, int *ret2, int *ret3)
{
    if(array_of_3 == NULL)
        return 0; //エラーコードを返す (偽)

    //値を変更するためにポインターをディレファレンスする。
   *ret1 = array_of_3[0];
   *ret2 = array_of_3[1];
   *ret3 = array_of_3[2];

   return 1; //エラーコードを返す (真)
}

/*
引数に配列型を使用するときは、配列は必ずポインターに変換されることに注意してください。これは
malloc関数で動的に確保したものでも、静的に定義した配列でも同じことが起きます。繰り返しになるが、
Cでは引数として渡された動的配列の長さを標準仕様で知ることができません。
*/
// 引数として配列のサイズを渡してください。
// でなければ、渡された配列の長さを知る術がありません。
void printIntArray(int *arr, size_t size) {
    int i;
    for (i = 0; i < size; i++) {
        printf("arr[%d] is: %d\n", i, arr[i]);
    }
}
/*
int my_arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int size = 10;
printIntArray(my_arr, size);
// "arr[0] is: 1" などが出力される。
*/

// 関数外で定義されている変数へアクセスするにはexternキーワードを使用します。
int i = 0;
void testFunc() {
  extern int i; //この変数iは外部変数iとして使用できる
}

// 外部変数を他のソースファイルから見えないようにする:
static int j = 0; // testFunc2()を使用する他のファイルは変数jに直接アクセスできない。
void testFunc2() {
  extern int j;
}

// staticキーワードはコンパイルユニット外での変数のアクセスを禁じ、プライベートにします。
// (大抵のシステムでのコンパイルユニットとは .c ソースコードのことを指す。)
// このstaticキーワードは(コンパイルユニットの)グローバルスコープと関数スコープどちらでも使用できますが、
// 関数スコープで使用すると挙動が変わり、アクセス制限をするのではなく、変数の寿命がプログラム終了まで延長されます。
// これはその変数が関数の実行が終了したあともメモリーにとどまり、グローバル変数と同じように
// 値を保持し続けることができるようになります。グローバル変数とは違って、変数は定義された関数のスコープ内のみで使用できます。
// 更に、staticで宣言された変数は初期値が与えられてなくても必ず0で初期化されます。
// **関数をstatic宣言することで変数と同じようにプライベートにすることができます。**

///////////////////////////////////////
// ユーザー定義の型と構造体
///////////////////////////////////////

// typedefキーワードを使えば型の別名をつけることができます
typedef int my_type; // my_type は int型の別名になった。
my_type my_type_var = 0; // int my_type_var = 0; と等しい

// 構造体は複数のデータを一つにまとめたものであり、上から下の順で定義されたメンバーがメモリーに割り当てられる:
struct rectangle {
  int width;
  int height;
};

// この構造体のサイズは必ずしも
// sizeof(struct rectangle) == sizeof(int) + sizeof(int)
// にならない、なぜならコンパイル時にシステムがメモリーを割り当てやすい位置にパッディングするからである。[1]

void function_1()
{
  struct rectangle my_rec = { 1, 2 }; // メンバーは定義時に初期化できる

  // "." で個別のメンバーへアクセスできる
  my_rec.width = 10;
  my_rec.height = 20;

  // 構造体へのポインターも定義できる:
  struct rectangle *my_rec_ptr = &my_rec;

  // ディレファレンスしてからメンバーの値をセットするのも良いが...
  (*my_rec_ptr).width = 30;

  // 可読性を高めるために"->"を使ってポインターから直接メンバーへアクセスすることもできる。
  my_rec_ptr->height = 10; // (*my_rec_ptr).height = 10; と同じ
}

// 毎回structを打たなくてもいいように構造体に別名をつけることができます
typedef struct rectangle rect; // rect = struct rectangle

int area(rect r)
{
  return r.width * r.height;
}

// typedefは構造体定義と同時に使えます:
typedef struct {
  int width;
  int height;
} rect; // 無名構造体にrectと名付けている。
// これで
struct rectangle r;
// を打たなくても
rect r;
// と楽に宣言・定義できる

// サイズが大きい構造体は値渡しの代わりにポインター渡しでコピー作成の時間・メモリー使用量増大を避けることができます:
int areaptr(const rect *r)
{
  return r->width * r->height;
}

///////////////////////////////////////
// 関数ポインター
///////////////////////////////////////
/*
実行時には、関数はプログラムに決められたメモリーアドレスにあります。関数ポインターは他のポインターとほとんど変わりません。
違うところは、ポインターを使って関数を呼び出せることです。これにより、関数の引数として他の関数をコールバックとしてわすことができます。
難しいところは、他の変数へのポインターとは表記法が違ってくることです。

例:str_reverse関数をポインターとして使う
*/
void str_reverse_through_pointer(char *str_in) {
  // 戻り値がvoid型fの名前がついた関数ポインターを宣言する。
  void (*f)(char *); // 引数も一緒に書きますが、指している関数と同じ戻り値と引数の型でなければなりません(引数名は入れずにただ型を列挙する)。
  f = &str_reverse; // 関数のアドレスを代入する(アドレスは実行時に決定される)。
  // f = str_reverse; これも正しくコンパイルできる - 配列同様ポインターに変換される
  (*f)(str_in); // 関数ポインターから実行する
  // f(str_in); // この表記法でも正しく実行できる
}

/*
同じ戻り値型と引数である限り、どの関数でも使えます。
可読性と単純性を実現するために、typedefが使われます。
*/

typedef void (*my_fnp_type)(char *);

// 下記で直背ポインター変数を宣言できます:
// ...
// my_fnp_type f;


/////////////////////////////////////
// printf()を用いて文字などを出力する
/////////////////////////////////////

//特殊文字:
/*
'\a'; // 警告 (ベル) 文字
'\n'; // 改行文字
'\t'; // タブ文字 (左揃えテキスト)
'\v'; // 垂直タブ文字
'\f'; // (フォームフィードの)新規ページ
'\r'; // 復帰文字
'\b'; // バックスペース文字
'\0'; // ヌル文字 - Cでは文字列終端文字として使用される。
//   hello\n\0. \0が明示的に文字列の終わりを表している。
'\\'; // バックスラッシュ
'\?'; // 疑問符
'\''; // シングルクォーテーションマーク
'\"'; // ダブルクォーテーションマーク
'\xhh'; // 文字コード(16進数) 例: '\xb' = 垂直タブ文字
'\0oo'; // 文字コード(8進数)  例: '\013' = 垂直タブ文字

// printf等で使われるフォーマティング:
"%d";    // 整数
"%3d";   // 整数最低3桁表示 (右揃え)
"%s";    // 文字列
"%f";    // 浮動小数点
"%ld";   // 長整数
"%3.2f"; // 小数点以下2桁、小数点以上最低3桁で表示される浮動小数点
"%7.4s"; // (文字列としての浮動小数点でも同じことができる)
"%c";    // 文字(単一)
"%p";    // ポインター 注:ポインターを渡すときには (void*) 型へ
         //                変換しなければならない。
"%x";    // 整数16進数表示
"%o";    // 整数8進数表示
"%%";    // "%" を挿入する
*/

///////////////////////////////////////
// 評価順序
///////////////////////////////////////

// 順位は上から下へ、一番上は優先順位が最も高い
//------------------------------------------------------//
// 演算子                                    |  優先順位  //
//------------------------------------------------------//
//  () [] -> .                              |  左から右  //
//  ! ~ ++ -- + = *(型) sizeof               |  右から左  //
//  * %                                     |  左から右  //
//  + -                                     |  左から右  //
//  << >>                                   |  左から右  //
//  < <= > >=                               |  左から右  //
//  == !=                                   |  左から右  //
//  &                                       |  左から右  //
//  ^                                       |  左から右  //
//  |                                       |  左から右  //
//  &&                                      |  左から右  //
//  ||                                      |  左から右  //
//  ? :                                     |  右から左  //
//  = += -= *= /= %= &= ^= |= <<= >>=       |  右から左  //
//  ,                                       |  左から右  //
//------------------------------------------------------//

/******************************* ヘッダーファイル **********************************

ヘッダーファイルはC言語の重要な役割で、ソースファイル間の依存関係の管理を
容易にすることや関数などの宣言を他のファイルに分けることができます。

ヘッダーファイル内は通常のC言語と変わりませんが、ファイル拡張子が ".h" になっており、
同じディレクトリー(フォルダー)に存在するなら` #include "ファイル名.h" `で
ヘッダーファイルで宣言した関数、定数などをソースファイルで使用できます。
*/

/*
セーフガードは#includeマクロを使用する際に、複数回宣言されるのを防ぎます。
特に互いを参照しあってしまう相互依存の場合に有効です。
*/
#ifndef EXAMPLE_H /* もし EXAMPLE_H が定義されていないならば、*/
#define EXAMPLE_H /* マクロ EXAMPLE_H を定義する。*/

// ヘッダーファイル内で他のヘッダーファイルを #include することができます。
#include <string.h>

/* 通常と同じように、マクロを用いて定数を定義できます。これは
ヘッダーファイルとそれを#includeしたソースファイルで使用できます。 */
#define EXAMPLE_NAME "Dennis Ritchie"

// 関数マクロも定義できます
#define ADD(a, b) ((a) + (b))

/*
引数である変数の周りに丸括弧がありますが、これはマクロの展開時に評価順序が
意図しないものにならないようにするためです。(例:関数 MUL(x, y) (x * y);
があるとします。MUL(1 + 2, 3) は(1 + 2 * 3)と展開され、間違った答えが帰ってきます。)
*/
// struct, typedefも同じように定義できます。
typedef struct Node
{
    int val;
    struct Node *next;
} Node;

// 列挙体も同じく、
enum traffic_light_state {GREEN, YELLOW, RED};

/*
関数プロトタイプもヘッダーファイルで宣言できます。ヘッダーファイルで定義を
書くのはよろしくないとされており、定義はソースファイルで記述することを
強く勧めます。
*/
Node createLinkedList(int *vals, int len);

/*
これ以外の要素はソースファイルに残します。過剰な#includeや定義は1つの
ヘッダーファイルには入れず、別の複数のヘッダーファイルかソースファイルに
分けてください。
*/

#endif // if系列マクロの終わり

関連記事、教材(一部英語)

CS50 日本語版 はハーバード大学が無料で公開しているコンピューターサイエンスコースで 字幕付きの動画と一緒にC, Python, SQL, HTML, CSS, Javascriptなどの言語を使った素晴らしいコースです。 C言語を学ぶ者は第1-5週目を受けることをおすすめします。

Learn C The Hard Way は有料だが、良い英語での教材です。

質問があるならば、compl.lang.c Frequently Asked Questions を見るのが良い。

インデンテーションや空白の使い方はどこでも一定であることが望まれています。たとえそのコードが画期的で実行速度が速くとも、 可読性が確保できなければ保守性に欠けます。良いコーディングスタイルの一つにはLinuxカーネルのものがあります。

それでも分からんことがあったら、GPTにかける前にググってこい。 Googleは友達だからな。

[1] 【C言語】構造体を作る上でのアライメントのお話。そもそもアライメントとは…


提案がありますか?それとも修正が必要ですか? GitHubレポジトリでIssueを開くか自分でプルリクエストを作ってください。

Adam Bardが初めて貢献し、後に8人の貢献者が更新してきました。