並べ替え
Quora Userさんのプロフィール写真

配列がポインタに自動変換され、添字アクセスがポインタへの算術演算を通じたアクセスと等価と定義し、同じもののように扱える、というアイデアを捨て、普通に別のものとして扱うようにする。

この仕様は、配列の範囲外アクセスチェックをするような言語拡張を事実上不可能にし、char,char*が日常的に使わなければならない「文字列」でもあると同時に「任意のバイト、バイト領域を扱えるオールマイティ」という高危険度データ型と不可分同一、という仕様とあいまって、「安全な文字列操作を、並みの人間では長期的にはほとんど不可能にする」という結果を招き、今となっては凄まじいクソ仕様というしかありません。現代でもバッファオーバーフロー攻撃による脆弱性の発見が後をたちませんが、元凶はほとんどここらへんと言っても差し支えないと思います。

まあ、当時はネットワークを通じた攻撃という概念がほとんどなかった頃なので、まさしく後知恵ですが、時空を越えた憑依ということで。(畏れ多い!)

もちろん、C言語は紛れもなく素晴らしい発明であり、人類文明に多大な貢献を果たしたことは、感謝と共に申し添えておきます。

松本 光一さんのプロフィール写真

creat 関数を create にする…というのは、Ritchie の言ったジョーク(?)ですが、PDP シリーズの乏しいリソース、容量が小さくて遅いハードディスク、今時のプリンタなど比較にならない低速のテレタイプ I/O コンソール、ということを考えると、何を提起しても馬鹿げていると一蹴されたかなと思います。

int がマシンによって 16ビットになったり 32ビットになったりしたのは、PDP シリーズや VAX シリーズとの互換性を考えるとやむを得なかったことですし、当時の低速な PDP では、32bit 値を扱うのはコストのかかる、つまり遅い処理になってしまっていたためです。じゃあ int16 とか、int32 を導入すれば、という意見もあるようですが、そうすると、UNIX のポータビリティが失われるのでデメリットしかなかったのです。また、BCPL のサブセットとしての B 言語、その発展としての C 言語を考えると、1ワードが必ずしも 8ビットや 16 ビットとは限らなかった当時のコンピュータを考えればビット数の規定はむしろ邪魔と言われたと思います。

また、配列の添え字チェックをしないとか配列とポインタの互換とかは当時としては苦肉の策で、チェックを入れたり分離したりすればただでさえ遅いマシンに遅い処理系が走ることになって開発者としては認められなかったでしょう。

Hideki Ikemotoさんのプロフィール写真

当時の事情を考えると大きく変えるのは難しいと思いますが、一つだけ挙げるならswitch文の仕様ですね。後世の言語にもこの仕様が引き継がれているものがあるので。

switch文 - Wikipedia
構文は以下の通り。 switch ( 制御式 ) { case 値1 : 文 文 ……… break ; case 値2 : 文 文 ……… break ; default : 文 } 上記の「case」ラベルはいくつでも記述することができる。caseラベルの「値」はコンパイル時に決まる整定数式 (integer constant expression) である必要がある。 この文は次のような手順で実行される。 制御式を評価し、整数値を得る。 その整数値がどれかのcaseで指定された値であるなら、そのcaseに引き続く文に飛ぶ。 どのcaseでも指定されていなければ、defaultに引き続く文に飛ぶ。 もしdefaultが記述されていなければ、何も実行せずにswitch文を抜ける。 ここで注意しなければならないのが、caseは ラベル に過ぎず、そのcaseより前からの実行から、そこでswitch文を抜けさせる働きはない点である(一般的には、次のcaseがあらわれる直前に break文 を置く)。このルールはフォールスルー (fall through) と言い、制御の流れが合流する動作をさせたい場合に便利であるが、一方でbreak文の書き忘れによる バグ 、ループを抜けるbreakと取り違える誤読によるバグなど、バグの温床として問題視されてきた。 そのため lint では、意図的にフォールスルーしていることを示す /* FALLTHROUGH */ などのコメントが記述されていない限り警告を出す。また、Cに類似した構文を採用した言語でも、 C# のように対策(後述)した言語仕様にされていることがある。 上記の例は、 if文 を羅列することで同様の動作を実現することができる。なおif文では比較対象の「値」が整定数式である必要がない点においてswitch文よりも柔軟である。 _tmp_ = 制御式 ; if ( _tmp_ == 値1 ) { 文 } else if ( _tmp_ == 値2 ) { 文 } ……… else { 文 } defaultは最後に記述される場合が多いが、必ずしも最後である必要はない。 switchによる分岐は以下のように do-while文 と組み合わせることも可能である。 switch ( count ) { default : do { printf ( "%d \n " , count ); count ++ ; case 0 : printf ( "%d \n " , count ); count ++ ; case 1 : printf ( "%d \n " , count ); count ++ ; case 2 : printf ( "%d \n " , count ); count ++ ; } while ( count ); } 例えば Duff's device ではそのような使われ方をしている。 C# でのswitch文はC言語と似たような見た目であるが、フォールスルーについての挙動は異なる。 switch ( 式 ) { case 0 : case 1 : // 式が0か1の時に実行 System . Console . WriteLine ( "Case 0 or 1." ); return 1 ; case 2 : System . Console . WriteLine ( "Case 2." ); goto case 3 : // case 3も実行 case 3 : System . Console . WriteLine ( "Case 3." ); break ; default : System . Console . WriteLine ( "Default." ); break ; // ここのbreakも省略不可 } C#では、caseラベルは文に付属する扱いとなるが、1つの文に複数のcaseラベルを付けることができる。また、C言語のようなフォールスルーは禁止されており、次のcaseラベル付きの文、あるいはswitchブロックの末端に、通常の制御フローで 到達してはならない 。すなわち、 break でswitchを抜ける、 return で関数ごと抜ける、 例外 を投げる、 無限ループ してそれ以上進まない [ 注釈 1 ] 、goto caseするなどの書き方が必要となる [ 2 ] 。goto caseにより、C言語ではフォールスルーを使って書くことができた、制御の合流を書くことができる。 Cや C++ では、switch文の制御式には整数型の式、またcaseラベルには整定数式しか使用できないのに対し、C#ではそれぞれ文字列型および文字列リテラルも使用できる。また、整数型あるいは整数型に準ずる値型のnull許容型 System.Nullable も使用することができる。 C# 7.0以降では型switchを使用できる。 // C# 7.0以降 switch ( obj ) { case int num when num < 0 : Console . WriteLine ( $"objは負の32bit整数{num}です。" ); break ; case string str when str . StartsWith ( "H" ): Console . WriteLine ( $"objはHから始まる文字列{str}です。" ); break ; case string str : Console . WriteLine ( $"objは文字列{str}です。" ); break ; default : Console . WriteLine ( "objは想定外の型あるいは値です。" ); break ; } Go では、caseに複数の値を指定できる。次のcaseの直前にfallthrough文を置くとフォールスルーになる。 PHP では、C#と同様、文字列にも、switch文が適用できる。 switch ( str ) { case "ABC" : 文A ; break ; case "XYZ" : 文B ; break ; case "123" : 文C ; break ; default : 文D ; break ; } PHPのswitch文においては、比較が === 演算子ではなく == 演算子で行われる。そのため、曖昧一致に起因し、開発者が予期しない動作となる場合がある。 構造化 された BASIC では、Select Caseステートメントが存在することが多い。このステートメントでは、文字列または整数を対象にできる。 Select Case str Case "ABC" 文 A Case "XYZ" 文 B Case "123" 文 C Case Else 文 D End Select Select Case age Case Is < 20 文 A Case 20 To 29 文 B Case 30 , 50 , 70 'Caseに複数の値を指定することができる 文 C Case E

breakを入れないと次のcaseに行くのですが、breakの入れ忘れによるバグが発生しがちです。

なのでデフォルトでbreak、あるいはcase文の最後にbreakやreturnを入れないとコンパイルエラーにする、それなら出来そうかなと。

Quora Userさんのプロフィール写真

直接走るコードを書けるからです。

PythonとかJavaとかが動くようにするためには、それを走らせる環境が必要で、順当に考えるとOSが載るような物が必要になる。 組み込みの一部にはOSが乗っていますが、小規模なデバイスはOSが無く、直接コードが走る必要があります。
 OSが要らず、プログラムがコンパクトで速ければ安いデバイスで済むわけです。
 例えばちょっとした製品に、OSが走る300円の32bitの高性能ARMマイコンではなく、30円の16ビットマイコンにすれば原価が大幅に下がります。 プログラム開発に多めのコストを吐き出したところで、1台1台の製品を作るときには270円ずつ安くなり1万台で270万円安くなります。 更に、小規模なデバイスは電源や周辺制御、基板や実装コストも安くなるのでこの価格差は拡大します(OSが動くようなチップは大抵BGAという実装で多層基板が必要ですが、小規模な物なら1層でもなんとかなるし両面程度で十分になる)
 ソフトウェア製品は、ソフトだけ売れば良くネットの時代になって量産コストがほぼ皆無だから開発コストだけ気にすれば良いが、組み込みは対象の製品を作れば作る分だけ材料の代金がかかる。

コンパクトなLinuxが動くボード

Linuxをそれなりに動かすなら、これの中央にあるMPUと左にあるメモリチップくらいは必要。 そして、それの電源やクロック回路等が上下と更に

直接走るコードを書けるからです。

PythonとかJavaとかが動くようにするためには、それを走らせる環境が必要で、順当に考えるとOSが載るような物が必要になる。 組み込みの一部にはOSが乗っていますが、小規模なデバイスはOSが無く、直接コードが走る必要があります。
 OSが要らず、プログラムがコンパクトで速ければ安いデバイスで済むわけです。
 例えばちょっとした製品に、OSが走る300円の32bitの高性能ARMマイコンではなく、30円の16ビットマイコンにすれば原価が大幅に下がります。 プログラム開発に多めのコストを吐き出したところで、1台1台の製品を作るときには270円ずつ安くなり1万台で270万円安くなります。 更に、小規模なデバイスは電源や周辺制御、基板や実装コストも安くなるのでこの価格差は拡大します(OSが動くようなチップは大抵BGAという実装で多層基板が必要ですが、小規模な物なら1層でもなんとかなるし両面程度で十分になる)
 ソフトウェア製品は、ソフトだけ売れば良くネットの時代になって量産コストがほぼ皆無だから開発コストだけ気にすれば良いが、組み込みは対象の製品を作れば作る分だけ材料の代金がかかる。

コンパクトなLinuxが動くボード

Linuxをそれなりに動かすなら、これの中央にあるMPUと左にあるメモリチップくらいは必要。 そして、それの電源やクロック回路等が上下と更に裏面に並んでいる。  左のUSBと右のLANは不要なら外せるが、それを切り詰めても半分くらいのサイズは残る。 メモリは512MB等で、クロックは1GHz等。 しかし、メモリもCPUの計算能力もOSにだいぶ持って行かれる。

直接走るコードを動かすならこんなボードで出来る

大きいのがMPUで内部にメモリも持っていて、外部には電源とかが少しあるだけ。さっきのLinuxボードの右上にあるチップがこのMPUと同じサイズ。 数十MHzの計算コアと、数百KBのプログラムフラッシュと数百KBのメモリがチップの中に収まっているのでCで書いたコンパクトなプログラムはそれを全て独占し、チップ1個で完結出来る。

Ichi Kanayaさんのプロフィール写真

型チェックも組み込みの文字列型も多重インクルードを防止するディレクティブもなくて構わない.そういう時代だった.

だがセミコロン,てめぇだけは駄目だ.セミコロンはデリミタじゃなくてセパレータであるべきだったんだ.

脚注

Naoto Yoshiokaさんのプロフィール写真

検索しやすい言語名にする。

taturouさんのプロフィール写真

関数定義を func で、変数定義を var で始めるようにします。

func main(void): int {

var n: int = 0;

return n;

}

こうすればパーサー書くのがとても楽になるので。

最近の言語は、みんなこんな感じですよね。

Morihiro Katoさんのプロフィール写真

わかりやすく、と言うならば。

表計算(Excelなど)で例えてみます。

Excelを使用する時、何気なく使っているセルにも必ずA1やA2などのアドレスが存在していることと似ています。

例えば、A2セルに=A1と入力すれば、A1セルの値がA2セルにも表示されます。

これはポインタ変数にアドレスを入れているのと似ています。

  1. #include <stdio.h> 
  2.  
  3. int main(void){ 
  4. int A1; 
  5. int *A2; 
  6. A1=9999; 
  7. A2=&A1; //excelでいうところの、=A1と同じ。 
  8.  
  9. //%d 数字を表示、A2はポインタ型なので不可能 
  10. printf("A1 value:%d\r\n",A1); 
  11. //printf("A2 value:%d\r\n",A2);できない。 
  12.  
  13. //%p Addressを表示、A1はint型なので不可能 
  14. //printf("A1 value:%p\r\n",A1);できない 
  15. printf("A2 value:%p\r\n",A2); 
  16.  
  17. //%p Addressを表示、両方可能 
  18. printf("A1 address:%p\r\n",&A1); 
  19. printf("A2 address:%p\r\n",&A2); 
  20.  
  21. //%d pointerを表示、A1はint型なので不可能 
  22. //printf("A1 pointer:%d\r\n",*A1); できない。 
  23. pr 

わかりやすく、と言うならば。

表計算(Excelなど)で例えてみます。

Excelを使用する時、何気なく使っているセルにも必ずA1やA2などのアドレスが存在していることと似ています。

例えば、A2セルに=A1と入力すれば、A1セルの値がA2セルにも表示されます。

これはポインタ変数にアドレスを入れているのと似ています。

  1. #include <stdio.h> 
  2.  
  3. int main(void){ 
  4. int A1; 
  5. int *A2; 
  6. A1=9999; 
  7. A2=&A1; //excelでいうところの、=A1と同じ。 
  8.  
  9. //%d 数字を表示、A2はポインタ型なので不可能 
  10. printf("A1 value:%d\r\n",A1); 
  11. //printf("A2 value:%d\r\n",A2);できない。 
  12.  
  13. //%p Addressを表示、A1はint型なので不可能 
  14. //printf("A1 value:%p\r\n",A1);できない 
  15. printf("A2 value:%p\r\n",A2); 
  16.  
  17. //%p Addressを表示、両方可能 
  18. printf("A1 address:%p\r\n",&A1); 
  19. printf("A2 address:%p\r\n",&A2); 
  20.  
  21. //%d pointerを表示、A1はint型なので不可能 
  22. //printf("A1 pointer:%d\r\n",*A1); できない。 
  23. printf("A2 pointer:%d\r\n",*A2); 
  24.  
  25. return 0; 
  26. } 

結果は以下のようになります。

  1. A1 value:9999 
  2. A2 value:0x7fff53919838 
  3. A1 address:0x7fff53919838 
  4. A2 address:0x7fff53919830 
  5. A2 pointer:9999 

0xではじまるのが実際のアドレスです。

よく見るとA1とA2のaddressの末尾が若干異なっています。

(3行目と4行目です。)

0xで始まるアドレスをExcelのアドレスに置き換えると以下のようになります。

  1. A1 value:9999 
  2. A2 value:A1 
  3. A1 address:A1 
  4. A2 address:A2 
  5. A2 pointer:9999 

まとめ:

ポインタを理解するときは、Excelに当てはめれば視覚化されてわかりやすくなるかもしれないです。

理解の助けになったら幸いです。

Hantani Sadahikoさんのプロフィール写真

逆にポインターはなぜ必要なのかを考える方が早いかも知れません。

コンピューターのプログラムはメモリに書き込まれて動いています。

BASICなどのプログラムは使う人がメモリを意識しません。どのメモリーのアドレスに保存されていても気にしないわけです。

ただし、C言語はそれでは困るのです。例えば下の方に書いてある「H/8 IO領域」というのはメモリ上に配置されたコンピューターを制御する特別なメモリです。読んだり、書いたりすることでCPUを制御できます。

メモリの値を直接読んだり、書いたりできるようにしたのが「ポインター」です。

例えばメモリの0xFFFF10に0xFFを書き込まないといけない場合、

char *a; とポインター型変数を宣言します。 ポインターとはアドレスを代入できる変数なのです。

a=0xFFFF10; とポインターにアドレスを代入します。

*a = 0xFF; と書くことで、アドレス0xFFFF10に0xFFを書くことができます。

char a;

char *b;

と宣言した時、aと*bが同じように変数として扱えます。

a=10;とした時

アドレス 値

0000   00

0001   00

0002   10 ←a

0003   00

0004   00

変数a にはアドレスと値の2つの情報があります。

変数bは*bと書けば値、bと書けばアドレスを保存できます。

b=0003;

とbにアドレスを入れて

*b=2

逆にポインターはなぜ必要なのかを考える方が早いかも知れません。

コンピューターのプログラムはメモリに書き込まれて動いています。

BASICなどのプログラムは使う人がメモリを意識しません。どのメモリーのアドレスに保存されていても気にしないわけです。

ただし、C言語はそれでは困るのです。例えば下の方に書いてある「H/8 IO領域」というのはメモリ上に配置されたコンピューターを制御する特別なメモリです。読んだり、書いたりすることでCPUを制御できます。

メモリの値を直接読んだり、書いたりできるようにしたのが「ポインター」です。

例えばメモリの0xFFFF10に0xFFを書き込まないといけない場合、

char *a; とポインター型変数を宣言します。 ポインターとはアドレスを代入できる変数なのです。

a=0xFFFF10; とポインターにアドレスを代入します。

*a = 0xFF; と書くことで、アドレス0xFFFF10に0xFFを書くことができます。

char a;

char *b;

と宣言した時、aと*bが同じように変数として扱えます。

a=10;とした時

アドレス 値

0000   00

0001   00

0002   10 ←a

0003   00

0004   00

変数a にはアドレスと値の2つの情報があります。

変数bは*bと書けば値、bと書けばアドレスを保存できます。

b=0003;

とbにアドレスを入れて

*b=20;

と代入すると

アドレス 0003に20が入ります。

アドレス 値

0000   00

0001   00

0002   10 ←a

0003   20←*b

0004   00

&を使うと、アドレスを調べることができます。

&aは0002になります。

b=&a とするとbのアドレスは0002になります。

その状態で*b = 30;とすると変数aの値を書き換えれます。

アドレス 値

0000   00

0001   00

0002   30 ←a ←*b

0003   20

0004   00

・・・

例えばメモリーを配列だと想像してもらうと、アドレスというのは配列の添え字になります。

char MEMORY[1000000];という大きな配列があると思ってください。

char *y; とポインターを宣言すると

*yというのはMEMORY[y]の配列の添え字を操作するイメージです。

y=100;

*y=10;

a = *y;

というのは、

y=100;

MEMORY[y]=10;

a=MEMORY[y];

と同じことです。

「*」は「MEMORY[ ]」の省略だと思ってください。

凄くイメージ重視の説明ですが。

Miyata Akiraさんのプロフィール写真

C言語のポインタについてのモヤモヤが解消した印象深い思い出があります。

ン十年も前、まだ中学生か高校に上がったばかりの頃だと思います。当時、ほぼ定期購読していたマイコン雑誌があって、ある号から私にとって革命的な連載が始まりました。

その頃は8ビットCPUのZ80やMC6800などで組んだ自作ハードウェアに16進でマシン語をちまちま打ち込んでいた時代です。そんな時、「自作のハードウェアなんてもう卒業しよう!これからはプログラミングを楽しむ時代だ!」と銘打った企画が登場したのです。

連載テーマは、当時少しは知られるようになったC言語のコンパイラの製作です。Cコンパイラ(のサブセット)をC言語でコーディングしていくのです。私にとって、実質的なプログラミングキャリアのデビューは、連載されたC言語のソース(しかもコンパイラという得体のしれない代物が相手)をひたすら熟読C言語を学びながらコンパイラの作成技法を学ぶことでした。

でも結果的にはそれが正解だったのです。

C言語のポインタを学んだ頃に抱いた疑問・誤解、およびそれらを克服するきっかけは何でしたか?に対するOgawa Kiyoshiさんの回答 でも似たような経験が語られていますが、特定のプログラミング言語に精通したければそのプログラミング言語の処理系(コンパイラ、インタプリタ)の中身を勉強するに限るということです。

ちょっと、その時のAh Hah!体

C言語のポインタについてのモヤモヤが解消した印象深い思い出があります。

ン十年も前、まだ中学生か高校に上がったばかりの頃だと思います。当時、ほぼ定期購読していたマイコン雑誌があって、ある号から私にとって革命的な連載が始まりました。

その頃は8ビットCPUのZ80やMC6800などで組んだ自作ハードウェアに16進でマシン語をちまちま打ち込んでいた時代です。そんな時、「自作のハードウェアなんてもう卒業しよう!これからはプログラミングを楽しむ時代だ!」と銘打った企画が登場したのです。

連載テーマは、当時少しは知られるようになったC言語のコンパイラの製作です。Cコンパイラ(のサブセット)をC言語でコーディングしていくのです。私にとって、実質的なプログラミングキャリアのデビューは、連載されたC言語のソース(しかもコンパイラという得体のしれない代物が相手)をひたすら熟読C言語を学びながらコンパイラの作成技法を学ぶことでした。

でも結果的にはそれが正解だったのです。

C言語のポインタを学んだ頃に抱いた疑問・誤解、およびそれらを克服するきっかけは何でしたか?に対するOgawa Kiyoshiさんの回答 でも似たような経験が語られていますが、特定のプログラミング言語に精通したければそのプログラミング言語の処理系(コンパイラ、インタプリタ)の中身を勉強するに限るということです。

ちょっと、その時のAh Hah!体験をシェアしてみたいと思います。


C言語の学習者にとってポインタは鬼門と言われています。特にポインタと配列とが混淆していることが問題で、初心者に限らず、次の3つの宣言文が何を宣言しているのかについては中級者でもすぐには答えられません。

  1. int **a; // (1) 
  2. int *b[3]; // (2) 
  3. int (*c)[3]; // (3) 

正解は、

  • (1)の "int **a;" は、aという名前(変数名)の変数を宣言しており、
    • その型は、intオブジェクトへのポインタへのポインタ
  • (2)の ”int *b[3];" は、bという名前(配列)の配列を宣言しており、
    • 配列サイズは3で、
    • 各要素の型はintオブジェクトへのポインタ
  • (3)の ”int (*c)[3];” は、cという名前(変数名)の変数を宣言しており、
    • その型はサイズ3のintオブジェクトの配列へのポインタ

図で書くと、こんな感じです。黄色で塗りつぶした部分が宣言対象のオブジェクトで、それらには変数名や配列名が付けられています。

では、これがコンパイラの中身とどのように関係しているのでしょうか?

カギは、「C言語の演算子の優先順位」と「Cコンパイラが内部生成する構文解析木データ」にあります。

まずは、演算子の優先順位について。

C言語では、式中での配列の添字演算子[]はポインタの間接参照演算子*よりも優先度が高く、宣言文でも同じ優先順位に従います。()を使って、優先関係を明示的に表せば、以下のようになります。

  1. int **a; // (1) → int *(*a); 
  2. int *b[3]; // (2) → int *(b[3]); 
  3. int (*c)[3]; // (3) → int (*c)[3]; 

次に、コンパイラについて。

Cコンパイラの前半部の処理は、C言語のソースコード(テキストファイル)を読み込み、C言語の文法に基づいて構文解析を行い、内部に構文解析木データを構築することです。後半処理では構築した構文解析木データの意味を解釈し、マシン語に翻訳します。

宣言文に対応する構文解析木データを作成するアルゴリズムは非常に単純です。ポインタと配列が絡む部分だけを抜き出せば、次の2つのルールだけです。

  1. ルール1: T | *x; ⇒ {pointer-to {T}} | x; 
  2. ルール2: T | x[n]; ⇒ {array[n]-of {T}} | x; 

矢印(⇒)の左側のパターンが現れたら右側のようなパターン(=構文解析木データ)に置き換えるのです。なお、縦棒(|)は型指定子(=型情報)と宣言子(=宣言対象)とを区切るための便宜上の印です。

実際に、例(1)で試してみます。まず、ルール1を適用すれば以下の通り。

  1. // (1) int **a; 
  2. int | *(*a); ⇒ {pointer-to {int}} | *a; 

再度、ルール1を適用して

  1. {pointer-to {int}} | *a; ⇒ {pointer-to {pointer-to {int}} | a; 

縦棒(|)の右側が識別子(名前)だけになれば、構文解析木データは完成です。読んで字の如く、aが「(int へのポインタ)へのポインタ」であることは明白です。

例(2)(3)も同様です。それぞれ、以下の通りです。

  1. // (2) int *b[3]; 
  2. int | *(b[3]); ⇒ {pointer-to {int}} | b[3]; 
  3. {pointer-to {int}} | b[3]; ⇒ {array[3]-of {pointer-to {int}}} | b; 
  4. // (3) int (*c)[3]; 
  5. int | (*c)[3]; ⇒ {array[3]-of {int}} | *c; 
  6. {array[3]-of {int}} | *c; ⇒ {pointer-to {array[3]-of {int}}} | c; 

縦棒(|)をイコール(=)と見做すと、まるで方程式を解いていくような感覚です。実際にもコンパイラ内部ではこのような処理を行なっているわけですが、人が紙の上で行うことも簡単です。英語が分かれば、何が宣言されていて、その型が何なのか一目瞭然でしょう。

もっと複雑な宣言文も、丹念に方程式を解いていけば、いずれ解が得られます。

逆もまた然りで、こういう型のオブジェクトを宣言したいと思ったら、逆方向にパターンを書き換えていけばよいのです。

こうしたことを、コンパイラのソースを読みながらはたと気がついた次第です。


まぁ、こういった経緯もあってか、私はポインタの悪夢からは早ばやと卒業しました。もちろん、マイコン少年として、ハードウェアやアセンブリ言語の知識もそれなりに持っていたので、そのことも助けになった部分はあります。

実は白状すれば、ポインタはもう大丈夫なんですが、const や volatile などの型修飾子が結構厄介で、目下の頭痛のタネになっています。

Takizawa Hiroshiさんのプロフィール写真

Cのセミコロンは文の終わりの記号ですが、せっかくなのでプログラミングでのセミコロンの歴史に関して以下の記事を参考にしながら考えてみたいと思います。

A Brief History of the Semicolon in Programming
Where did semicolon usage come from anyway?

英語でのセミコロンの意味ですが、コンマ(,)より強くピリオド(.)より弱い文の区切りだそうです。

「;」セミコロンと「:」コロンの違いとは?それぞれの意味と使い方 [英語] All About
英語のセミコロン「;」やコロン「:」。よく目にはするものの、その意味や使い方、違いや使い分けについては意外にピンとこないかもしれません。知ってしまえば、日常的なメモやメールのやり取りのほか、ビジネスメールにも役立つこと間違いなしです!

上のサイトで例文として

I went to the library; Jonathan went to the theater.

「私は図書館に行き、ジョナサンは劇場に行った」

が挙げられていてセミコロンの前と後ろは独立の文で、しかも関連が強い場合に使うようです。

さてプログラミングの話に進めましょう。

世界で初めての高級言語はFORTRANと言われています。

初期のFORTRANではセミコロンは使われていません。基本的には1行一文です。

FORTRANのプログラムはパンチカードにパンチして計算機に入力していました。

By ArnoldReinhold - Own work, CC BY-SA 3.0, File:Punched card program deck.agr.jpg - Wikimedia Commons

これが一つのプログラムでカード一枚がFORTRANの一文です。(注:写真の最初の一枚はFORTRANではな

Cのセミコロンは文の終わりの記号ですが、せっかくなのでプログラミングでのセミコロンの歴史に関して以下の記事を参考にしながら考えてみたいと思います。

A Brief History of the Semicolon in Programming
Where did semicolon usage come from anyway?

英語でのセミコロンの意味ですが、コンマ(,)より強くピリオド(.)より弱い文の区切りだそうです。

「;」セミコロンと「:」コロンの違いとは?それぞれの意味と使い方 [英語] All About
英語のセミコロン「;」やコロン「:」。よく目にはするものの、その意味や使い方、違いや使い分けについては意外にピンとこないかもしれません。知ってしまえば、日常的なメモやメールのやり取りのほか、ビジネスメールにも役立つこと間違いなしです!

上のサイトで例文として

I went to the library; Jonathan went to the theater.

「私は図書館に行き、ジョナサンは劇場に行った」

が挙げられていてセミコロンの前と後ろは独立の文で、しかも関連が強い場合に使うようです。

さてプログラミングの話に進めましょう。

世界で初めての高級言語はFORTRANと言われています。

初期のFORTRANではセミコロンは使われていません。基本的には1行一文です。

FORTRANのプログラムはパンチカードにパンチして計算機に入力していました。

By ArnoldReinhold - Own work, CC BY-SA 3.0, File:Punched card program deck.agr.jpg - Wikimedia Commons

これが一つのプログラムでカード一枚がFORTRANの一文です。(注:写真の最初の一枚はFORTRANではなくJCLとよばれるもので、この一群のカードはFORTRANのプログラムですとOSに伝えるためのものと思われます。)

カード一枚で一文なので文(ステートメント)の終わりと特別な記号で表す必要はありません。

カード一枚で収まりきれない文をどうするかという疑問が湧くと思いますが、そのときは「継続行」仕組みで対処します。

実際のプログラミン作業ですが、カードにパンチしたプログラムを連続用紙に印字してもらって机上デバッグをします;机の上のプリントアウトを見て間違いを見つける作業です。下の写真はまさにFORTRANプログラムのプリントアウトです。

実際のFORTRANプログラムのコードはこんなものです。

この方式はIBMの80桁のカードを前提とした仕様です。

まず作ってしまうアメリカに対して、理論的な基盤、規格にこだわるヨーロッパを中心に合理的な言語の規格として提案されたのがALGOLという言語です。

ALGOLは入力機構とは独立にプログラムを記述できる言語を目指して規格を作りました。当然言語としての書法はかなり英語の影響を受けています。紙に英文を書くようにプログラムを記述できるということです。

1行一文方式だと1行の情報量が少なく、1行に複数の文を記述することを想定し、最初の英文の例のセミコロンを着想したのではないでしょうか。

1960年のALGOL60という規格で文の区切りとしてセミコロンが導入されました。ALGOLは、FORTRANとは異なり文法理論に則った構文となっています。コンピュータサイエンスを学ばれた方はBNF(バッカス・ナウア記法)という文法を記述する記法をご存知だと思います。BNFはALGOLの構文を記述するために発案されたものです。このバッカスはFORTRANの開発者のジョン・バッカスのことです。彼もALGOLの委員でありヨーロッパ中心といってもアメリカを含めた当時の一流の研究者、技術者が策定した規格です。

ALGOL60のその後の手続き型言語とコンピュータサイエンスに多大な影響を与えています。

以下がALGOL60のコードの一部分です。

  1. FOR i := 0 STEP 1 UNTIL 999 DO  
  2. BEGIN  
  3. IF candidates[i] # 0 THEN  
  4. BEGIN  
  5. write(1,i);  
  6. text(1," is prime*N")  
  7. END  
  8. END; 

5行目にセミコロンが使われていてwrite文とtext文の区切りとして使われています。

余談になりますが私の大学院時代の指導教官の清水留三郎先生は日本で初めてALGOLのコンパイラをインプリメントした人です。

1960年代にセミコロンを採用した言語にIBMのPL/Iがあります。PL/IはFORTRAN,COBOL,ALGOLの機能を包含しようとした野心的なプログラミング言語です。

PL/Iのコードの一部を示します。

  1. DO I = 1 TO LENGTH(INPUT_TEXT); 
  2. HUO0 = SUBSTR(INPUT_TEXT,I,1); 
  3.  
  4. IF HUO0 = ' ' THEN DO; 
  5. HUO1 = ' '; 
  6. END; 
  7. ELSE DO; 
  8. HUO1 = ASCII_TO_CHAR((CHAR_TO_ASCII(HUO0) + ENCRYPT_KEY)); 
  9. END; 
  10.  
  11. SUBSTR(OUTPUT_TEXT,I,1) = HUO1; 
  12. /*PUT SKIP LIST('I = ' || I);*/ 
  13. END; 

この例で注目してもらいたいのはENDの前の文です。ENDの前の文にも全てセミコロンがついています。ALGOLは文の「区切り」としてセミコロンを使っているためENDの前の文にはセミコロンがついていません。PL/Iではセミコロンを文の終端の必須のマークとして使っています。

ここで改行を終端としないメリットを考えてみたいとおみます。

PL/Iの例を一部を編集してみました。

  1. ELSE DO; 
  2. HUO1 =  
  3.       ASCII_TO_CHAR( 
  4.        ( 
  5. CHAR_TO_ASCII(HUO0) 
  6. +  
  7. ENCRYPT_KEY 
  8. ) 
  9.       ); 
  10. END; 

一つの文を改行して読みやすくすることができます(好みの問題もありますが)。

C言語(1972年)はALGOLとPL/Iから影響を受けていてセミコロンを文の終端としています。

C言語が文の区切りではなく文の終端としてセミコロンを使ったのはコンパクトなコンパイラーをつくるためです。

ALGOLのBEGIN, ENDを { }にしてしまったのもC言語です。

Cの仕様とコンパイラがシンプルであったこと、さらに米国の独禁法の関係からUnixに含まれるかたちでCのソースコードが無償で公開されたことによりCが現在のITに多大な影響を与えます。

C言語とUnixは多くの大学や研究所のマシンに移植され活発にさまざまなソフトウェアがCで開発されました。インターネットのプロトコルスタックのほとんどはCで記述されています。プログラミング言語を開発するための様々のツールもCで開発されています。最初のWeb技術は、C言語にオブジェクト機能を追加したObjective Cで開発されています。

この有名なプログラムもC言語とともに広がりました。

  1. #include <stdio.h> 
  2.  
  3. main( ) 
  4. { 
  5. printf("hello, world\n"); 
  6. } 

C言語はQuoraをはじめ皆さんがよく見るスマフォのアプリやWebのアプリを作るためには最適とは言えません。現在、Pythonをはじめ様々言語やフレームワークが利用できますが、それらはC言語で記述されたさまざまなソフトウェア資産(人材も含む)の上に作られていることを忘れないで欲しいと思います。

Kengo Nakajimaさんのプロフィール写真

詳細な歴史をものすごく簡略化して言うと、C言語は、UNIXオペレーティングシステムをアセンブリ言語で書く作業が人間にはつらすぎるという問題を解決するために、アセンブリ言語を置き換える最良の言語として作られました。また、アセンブリ言語は、マシン語(0と1のビット列)を直接入力する作業が人間にはつらすぎるという問題を解決するために、マシン語を置き換える最良の言語として作られました。

つまり置き換えは、マシン語>アセンブリ言語>いくつかの言語>C言語 という感じで進んできました。C言語も完全ではないため、C言語の次の置き換え候補も、当然、さまざまな人が考案しています。

C言語の問題点を解決しようとする言語は、List of C-family programming languages - Wikipedia を参照するまでもなく、たくさん派生しています。C++,C#,Java,Go,JavaScript,Rust,Ruby,PHP,Python,Perl など、現在広く使われている多くの言語が、C言語の派生言語です。このリンク先には、Go言語がないですね、驚きです!しかしGo言語を作った人たちは、Go言語はC言語の問題を解決する言語だと主張しています。

さて、C言語から派生したこれらの言語のなかで、JavaやJavaScriptやC#,PHP,Python,Rubyなどの言語は、C言語の機能の

詳細な歴史をものすごく簡略化して言うと、C言語は、UNIXオペレーティングシステムをアセンブリ言語で書く作業が人間にはつらすぎるという問題を解決するために、アセンブリ言語を置き換える最良の言語として作られました。また、アセンブリ言語は、マシン語(0と1のビット列)を直接入力する作業が人間にはつらすぎるという問題を解決するために、マシン語を置き換える最良の言語として作られました。

つまり置き換えは、マシン語>アセンブリ言語>いくつかの言語>C言語 という感じで進んできました。C言語も完全ではないため、C言語の次の置き換え候補も、当然、さまざまな人が考案しています。

C言語の問題点を解決しようとする言語は、List of C-family programming languages - Wikipedia を参照するまでもなく、たくさん派生しています。C++,C#,Java,Go,JavaScript,Rust,Ruby,PHP,Python,Perl など、現在広く使われている多くの言語が、C言語の派生言語です。このリンク先には、Go言語がないですね、驚きです!しかしGo言語を作った人たちは、Go言語はC言語の問題を解決する言語だと主張しています。

さて、C言語から派生したこれらの言語のなかで、JavaやJavaScriptやC#,PHP,Python,Rubyなどの言語は、C言語の機能のうち、システムプログラミングのための機能、つまり指定したメモリアドレスに直接アクセスしたり、マシン語を直接生成して実行したりする機能を捨ててできなくすることで、生産性と安全性を高めました。そのため当然ですが、ハードウェアを直接操作するプログラム(OSなど)を作りたい場合には、こうした言語は不向きです。しかし反対に、GUIアプリケーションやゲーム、Webサーバなどを作るときには、C言語よりも少ないコード量で、安全なプログラムを早く作ることができます。現在ではアプリケーションの用途は多岐に渡るので、用途ごとに特有の課題をうまく解決する言語が乱立するようになりました。

C言語以外でシステムプログラミングが全くできないということはありません。C言語から派生した言語の多くが、C言語で書かれた外部モジュールを読み込んで使えるように実装されています。C言語のシステムプログラミングの能力をこうした外部モジュールを経由して利用することができます。ただし、オペレーティングシステムのように、プログラムほとんど全体でシステムプログラミングが必要であるようなプログラムの場合は、外部モジュールを用いた実装方法は明らかに向いていません。

現在、オペレーティングシステム(OS)を実装することができるC言語の派生言語で有力なものは、Go言語とRust言語です。特にGo言語が突出しています。

Go言語を用いてOSを作ろうというプロジェクトはいくつかありますが、ソースが小さくて読みやすいものを紹介します:

achilleasa/gopher-os

Go言語はアセンブリ言語で書かれたモジュールを読めるようになっているので、OSのブートストラップのところだけアセンブリで書かれていますが、それ以外はすべてGoで書かれています。ブートストラップのところは厳密に正確なマシン語が必要であるため、C言語で書かれたOSであっても、Cからアセンブリ言語のモジュールを読むように実装されています。

また、ls,cp,rm,ln,cat,shなど、UNIXの重要な要素である、基本ツール群もCで書かれていますが、それらすべてをGoで書き直すプロジェクトがあります。ソースがC版とどう違うのかを見ると楽しめるでしょう。

ericlagergren/go-coreutils

Quora Userさんのプロフィール写真

「偉い」とか great と言うのが、そもそも「どういう事」なのか、よく分かりませんが、

よく分からないなりに言うと(汗)、

「かなり偉い」

と私は思います。

なぜなら、現在よく使われているプログラミング言語のほとんど(※1)、および OS のカーネルは(※2)、C で書かれているからです。

もし C が無かったとしたら、それらの多くは、今なおアセンブリー言語で書かれていたでしょう。

あるいは C に代わり得る別の言語がいずれ出現したでしょうが、C が 1972年に出回ってから、Rust が出るまで、50年近く掛かってしまった事を考えると、やはり C は偉大な発明だったと思います。

C の「偉さ」に比肩し得るのは、C で作られなかった言語達、つまりアセンブリー言語、FORTRAN、COBOL、PL/I、ALGOL、Pascal、あたりではないでしょうか。 彼らにとって「偉い」のは機械語であり、アセンブリー言語ということになるでしょう(※3)。

しかし彼らは、C ほど多くのモノを作り出すには至りませんでした。 私の私的な感覚ですが、C 以前と C 以降では——こういう言い方が適切なのかどうかよく分からないのですが——、プログラミングに対するプログラマーの取り組みと言うか姿勢と言うか哲学と言うようなものが、大きく変わった、という感じを持っています。
これは UNIX OS の影響が大きかったという事も

「偉い」とか great と言うのが、そもそも「どういう事」なのか、よく分かりませんが、

よく分からないなりに言うと(汗)、

「かなり偉い」

と私は思います。

なぜなら、現在よく使われているプログラミング言語のほとんど(※1)、および OS のカーネルは(※2)、C で書かれているからです。

もし C が無かったとしたら、それらの多くは、今なおアセンブリー言語で書かれていたでしょう。

あるいは C に代わり得る別の言語がいずれ出現したでしょうが、C が 1972年に出回ってから、Rust が出るまで、50年近く掛かってしまった事を考えると、やはり C は偉大な発明だったと思います。

C の「偉さ」に比肩し得るのは、C で作られなかった言語達、つまりアセンブリー言語、FORTRAN、COBOL、PL/I、ALGOL、Pascal、あたりではないでしょうか。 彼らにとって「偉い」のは機械語であり、アセンブリー言語ということになるでしょう(※3)。

しかし彼らは、C ほど多くのモノを作り出すには至りませんでした。 私の私的な感覚ですが、C 以前と C 以降では——こういう言い方が適切なのかどうかよく分からないのですが——、プログラミングに対するプログラマーの取り組みと言うか姿勢と言うか哲学と言うようなものが、大きく変わった、という感じを持っています。
これは UNIX OS の影響が大きかったという事も有るかと思います。 インターネットも UNIX が無ければ発達しなかったでしょう、つまり C が無ければ……(C は UNIX を書くために作られた)。

ところで、こうした「低層な観点」とは別に、やたらと偉い言語が1つあります。

LISP です。

先述した、「プログラミングに対するプログラマーの哲学」に、LISP は C に劣らず強い衝撃と影響をもたらした、と私は思います。 ※1で引用した記事における「不動の第1位」は JavaScript ですが、LISP 無くして JavaScript は生まれなかったでしょう。

JavaScript が今やどれほど偉いのか、それは説明を要しますまい。 LISP の血を受け継ぎ、C/C++ で書かれた JavaScript が、「不動の第1位」である事を考えると、C と LISP の偉さが、なんとなく分かるような気がします。


※1 正直言って、私はこのリンク先記事で言及されている 20言語のソースコードを見た事は無いのだが、これらの9割が C か C++ で書かれているだろう事には千円くらい賭けてもよい。

※2 「OSの開発にC++よりもCが使われていることが多い理由は何ですか?」における Kurimoto Shingo 様回答に付けられた Takahashi Takahashi 様コメントに、C が使われる理由が簡潔に書かれている。

※3 Pascal コンパイラーを Pascal で書いた実例が有るそうなので、Pascal はこれらの中ではやや別格——つまり「少し偉い」——かも知れない。 FORTRAN や COBOL で自言語コンパイラーが作れないとは言えないが、面倒過ぎて挑戦者が現れないであろう。

Ogawa Kiyoshiさんのプロフィール写真

C言語のポインタを学んだ頃に抱いた疑問・誤解と克服

1. C言語規格の意味がコンパイラを書いてみるまでわからなかった。コンパイラを書いてみるとC言語規格の未定義、未規定、処理系定義が、プログラマの精神に基づき、規格がないCPUの発展を妨げない範囲で、自由にプログラムが書けるようにするものであることがわかった。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/C99RationaleV5.10.pdf

  • Trust the programmer.
  • Don’t prevent the programmer from doing what needs to be done.
  • Keep the language small and simple.
  • Provide only one way to do one operation
  • Make it fast, even if it is not guaranteed to be portable.

2. 自分の書いたコンパイラが、メモリ管理がずさんで、デバッグモード以外は暴走する。静的検査、MISRA Cなどの規則に基づいて検査するとかなりメモリ周りの不具合が取れそう。

3. CPUが16bitから32bitになってメモリの管理方法が複雑になり、わけがわからなくなった。複数のOSを同時に利用できるようになり便利になっ

C言語のポインタを学んだ頃に抱いた疑問・誤解と克服

1. C言語規格の意味がコンパイラを書いてみるまでわからなかった。コンパイラを書いてみるとC言語規格の未定義、未規定、処理系定義が、プログラマの精神に基づき、規格がないCPUの発展を妨げない範囲で、自由にプログラムが書けるようにするものであることがわかった。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/C99RationaleV5.10.pdf

  • Trust the programmer.
  • Don’t prevent the programmer from doing what needs to be done.
  • Keep the language small and simple.
  • Provide only one way to do one operation
  • Make it fast, even if it is not guaranteed to be portable.

2. 自分の書いたコンパイラが、メモリ管理がずさんで、デバッグモード以外は暴走する。静的検査、MISRA Cなどの規則に基づいて検査するとかなりメモリ周りの不具合が取れそう。

3. CPUが16bitから32bitになってメモリの管理方法が複雑になり、わけがわからなくなった。複数のOSを同時に利用できるようになり便利になったので原理は理解できていなくても大丈夫。

まとめ

C言語のポインタはアセンブラを書けばわかる。知識は不要。アセンブラで書かない処理をC言語のポインタで書くのは、無理筋。ポインタがわからないのではなく、書こうとしている処理がわかっていない。

CコンパイラかOSを書いてみれば、いかにCPUを効率的に使うための言語であり、CPUまわりの記述をするための道具であることがわかる。アプリを書くための道具ではない。

C言語のポインタが難しいのではない。難しいことをポインタで書こうとするのが間違いだと気がつけばよい。

C言語のポインタなど文法から学ぼうとするのが無理で無駄。コンパイラ、OSなどのC言語で書かれているものから学べばよい。

Quora Userさんのプロフィール写真

自分のブログ URL を1つ書けば済むのですが、Quora では「宣伝や商業目的でQuoraユーザーたちを外部サイトへ誘導」する回答はスパムと見なされるので、そのブログ内容を自分で(ここの回答向けに適宜編集して)再掲します。


1.はじめに

C のポインタが「難しい」と言われる理由はいくつかありましょうが、大まかに言って次の3つの理由からだろうと私は考えています。

(1)C におけるポインタの文法が変な書き方で分かりにくい。
(2)分かりやすく書かれた書籍が非常に少ない。
(3)他の言語ではポインタが使われないように見えるので、何のために使うのかが分かりにくい。

本回答では(3)に重点を置いて説明します。 誤解が無いように書くと長文になってしまうのですが、ゆっくり読めば、全く難しくありません。 必ず分かるという自信を持って、あせらず、ゆっくり読んでみてください。


2.ポインタとは、そもそも何者なのか

ポインタ(pointer)という言葉は point-er ですから、「指し示す者」と訳せます。 猟犬にポインターという犬種がいますが、あれは、仕留めた獲物の場所を教えてくれるので、そういう名前になったのです。

C のポインタも、それに似ています。 C のポインタとは、何かを指し示すための型、変数です。

一体、何を指し示すのか……それは、「メモリ(memory)に格納されている何か」です。

「何か」は、in

自分のブログ URL を1つ書けば済むのですが、Quora では「宣伝や商業目的でQuoraユーザーたちを外部サイトへ誘導」する回答はスパムと見なされるので、そのブログ内容を自分で(ここの回答向けに適宜編集して)再掲します。


1.はじめに

C のポインタが「難しい」と言われる理由はいくつかありましょうが、大まかに言って次の3つの理由からだろうと私は考えています。

(1)C におけるポインタの文法が変な書き方で分かりにくい。
(2)分かりやすく書かれた書籍が非常に少ない。
(3)他の言語ではポインタが使われないように見えるので、何のために使うのかが分かりにくい。

本回答では(3)に重点を置いて説明します。 誤解が無いように書くと長文になってしまうのですが、ゆっくり読めば、全く難しくありません。 必ず分かるという自信を持って、あせらず、ゆっくり読んでみてください。


2.ポインタとは、そもそも何者なのか

ポインタ(pointer)という言葉は point-er ですから、「指し示す者」と訳せます。 猟犬にポインターという犬種がいますが、あれは、仕留めた獲物の場所を教えてくれるので、そういう名前になったのです。

C のポインタも、それに似ています。 C のポインタとは、何かを指し示すための型、変数です。

一体、何を指し示すのか……それは、「メモリ(memory)に格納されている何か」です。

「何か」は、int型変数の場合もありますし、char型変数だったり、配列だったり、あるいは何らかの構造体だったりします。 場合によっては関数だったりします。 これら「メモリに格納されている何か」達を、総称して「オブジェクト(object)」とも呼びます。

関数は実行出来るオブジェクト、定数やリテラル(即値)は参照しか出来ないオブジェクト、int型変数は整数を格納するオブジェクト、float型変数は浮動小数点数を格納するオブジェクト、ポインタ変数は「オブジェクトを指し示すオブジェクト」、なのです。

では、C で扱うオブジェクト(変数・定数・即値・関数)が置かれている「メモリ」とはどういうモノか……それは「同じ大きさの小箱を、たくさん一列に並べた」ようなもので、それぞれの小箱にアドレス(address)という整数値の通し番号が付けられています。 C プログラム上においては、この小箱1つのサイズは1バイトであり、それは char型サイズと同じである事が、C の規格で決まっています(実際のコンピュータのメモリが、たとえ1ワード14ビットであろうが関係なく、C のメモリの小箱1つは1バイトです)。

あるオブジェクトが、小箱いくつ分のサイズなのか(何バイトなのか)は、そのオブジェクトの型(type)によって決まります。 float型なら4つで char型なら1つ、などです(char型以外の型サイズは、処理系依存です)。

1つの型は、メモリ上で連続した小箱に置かれます。 float型が、2つの小箱と、別のところにある2つの小箱に分断される事はありません。 また、配列は全要素が連続した小箱に置かれます。 従って、オブジェクトの先頭アドレスを使えば――それがどんなに大きなサイズであっても先頭アドレスだけで――、それを指し示す事が出来ます。

(オブジェクトが、複数のオブジェクトの組み合わせから成る場合、それらがメモリのあちこちに分散する事は普通にありますが、「最初の・先頭オブジェクト」の型1つは、やはり連続した小箱に置かれます)

ポインタは、対象が置かれているメモリ領域の「先頭アドレス値と型を格納する」ことによって、対象オブジェクトを指し示します。 アドレス値と型があれば、対象のオブジェクトがメモリの「どこにあり」「どこまであるのか」が分かるからです。

たとえて言えば、ポインタとは、「住所を書き留めるための小さな紙きれ」のようなものです。 「東京都千代田区1-1」という住所を、小さな紙切れに書いて机上に置いておいたり、コピーしたり、誰かに渡す事は簡単に出来ますが、「皇居そのもの」を机上に置いたりコピーしたり誰かに渡すなんて事は、まず不可能です。

「皇居オブジェクト」そのものを扱うのは大変ですが、「皇居の住所オブジェクト」は、簡単に扱える……住所(address)という概念は、素晴らしいと思いませんか?

なお、C のアドレス値は整数なのですが、ポインタは整数型ではありません。 ポインタは対象オブジェクトの型情報も持つからです。 また、ポインタ変数が持つアドレス値の具体的な値は、特殊な場合を除き、気にする必要はありません。

(ポインタがどういう仕組みなのかは処理系依存であり、printf( ) 関数の出力変換書式 %p でアドレス値を表示する際の出力形式も、処理系依存です)


3.基本的な文法

ここでは、C のポインタにまつわる、基本的な3つの書き方、「宣言」、「アドレス取得」、「参照」について触れます。

3.1 ポインタ型変数の宣言

ポインタ型変数を宣言するには、次のように書きます。

  1. int * a; /* 「int型領域を指すポインタ型変数」 a を宣言 */ 
  2. char * b = "ABC"; /* 「char型領域を指すポインタ型変数」 b を宣言 */ 

型名の後ろにポインタ宣言子の * が有る変数宣言は、「ポインタ型変数」宣言です。 指定した型の領域のアドレス値に限り格納できる変数が、この宣言後に使えるようになります。 ポインタ型変数は、単にポインタとも呼びます。

上記例のポインタ a は、int型領域しか指し示すことが出来ません。 他の型の領域のアドレスを設定すると、コンパイル・エラーになります。 ポインタは型情報を持つからです。

(なお、文字列の初期化には特別な決まりがありますが、ここでは割愛します)

「皇居のたとえ」で言えば、住所をメモするための紙切れを用意したり、すでに住所が書かれている紙切れを用意する、というのが、この「ポインタ型変数の宣言」です。

型に void を指定すると、「特に決まった型を指さないポインタ」が宣言されます。 void * 型ポインタは、キャスト(型を強制的に読み替えること、あまり推奨はしません)によって、様々な型の領域を指し示す事が出来ます。

  1. void * c; /* 「指す領域の型を特定しないポインタ」 c を宣言 */ 

3.2 アドレス取得

単項アドレス演算子 & を使うと、その対象オブジェクトが占有しているメモリ領域の先頭アドレス値が得られます。 「単項」というのは、マイナス符号のように、「相手が1つだけ」という意味です(= とか / は、2つの相手が要るので、2項演算子です)。 以下のように使います。

  1. int d = 365; /* 「int型の領域を占有し整数を格納する変数」 d を宣言 */ 
  2. a = &d; /* a には d の先頭アドレス値が入る */ 

上記例で、&d は、変数 d のアドレス値、すなわち d の「住所」を示しています。 変数 a は int型専用ポインタですから、int型のアドレスである &d が代入出来ます。

「皇居のたとえ」で言えば、皇居の住所を調べるのが、アドレス演算子の役割だ、と言えます。

3.3 参照

ポインタに格納されたアドレス値そのものを見ても、そのアドレスに格納されているモノ自体については全く分かりません。 「東京都港区赤坂2-3」という住所だけ見ても、そこに何があるか、何が建っているか、誰がいるかは分からないのと似ています。

何らかのアドレス値を格納しているポインタに、単項参照演算子 * を付けると、対象としているアドレスの内容値、つまりメモリの内容が得られます。 この参照演算子 * は、ポインタ宣言子の * とは、全く意味が違う「別物」です(これは混乱を招く文法だと思います……)。 以下のように使います。

  1. printf( "%d\n", *a ); /* 参照した内容値 365 が表示される */ 
  2. *a = 123; /* 参照した内容値が書き換えられる */ 
  3. printf( "%d\n", d ); /* 書き換えられたので 123 が表示される */ 

「参照」とは、「皇居のたとえ」で言えば、「東京都千代田区1-1」と書かれた紙片を見て、その住所へ実際に出かけてみるようなものです。 変な・架空の住所が書かれていたり、白紙だと困ってしまいます。 * で参照されるポインタには、あらかじめ何らかのアドレス値が入っていなければなりません。

上記の例で示した「参照先の書き換え」は、たとえて言えば「住所が書かれた紙片を見て、その住所にある建物を建て替える」ようなものです。


4.局面その1「そのモノ自体を持ち回りたくない!」

ここからは本回答の本題、ポインタの「使いどころ」について説明します。

「メモリ上にある int や char や struct や配列は、その変数名で直に示せるじゃないか、わざわざポインタで示さなくても」……と思いませんか。

ポインタで書けるプログラムは、配列でも書けたりしますし、配列の方が分かりやすい感じがします。 一体、どんな時にポインタを使いたくなるのでしょうか。

関数の外側で宣言した変数は、どこからでも変数名でアクセス出来ますので、ポインタの出番は無さそうです。 1つの関数の中だけで使う変数も、同様に、ポインタの出番は無さそうです(アルゴリズムの都合でポインタにしたい場合はあるかも知れませんが)。

……ということは、ポインタが役に立ちそうなのは、「関数の中で宣言した変数・領域を、別の関数で使いたい時ではないか」、と予想出来るのではないでしょうか。

呼び出し元の関数が持つ値や、宣言した変数(の内容値)を、別の関数で使いたい時は、引数として渡す事が出来ます。

引数のある関数を呼び出す時、C では呼ぶ側の実引数の内容値が、呼ばれ側の関数の仮引数にコピーされます。 int型の引数なら4ないし8バイト、char型の引数なら 1バイト、必ずコピー動作が入るのです。

実用的なプログラムでは、そこそこ大きな構造体や配列を、関数の間で持ち回りたい、という事が、よくあります。

しかし、関数の引数に構造体や配列を書くと、関数を呼び出す度に、大量のコピー動作が発生してしまいます。 1回や2回程度なら許容範囲かも知れませんが、ループの中で関数呼び出しが有ったりすると、処理時間がそれだけ掛かります。

そこで、呼ぶ側で「持ち回りたい領域」の先頭アドレスを用意して、これを引数として関数を呼ぶ、という事が考え出されました。 アドレス値を渡すので、受け取る側の関数は、仮引数をポインタ型にしなければなりません。

呼ばれ側で、ポインタに * を付けて参照すれば、目的の領域を扱う事が出来ます。

ポインタのサイズは、多くの場合、4~8バイトに過ぎませんが、ポインタ1つで、数百、数千、数メガバイト、それ以上の領域を「持ち回る」事が出来ます。

「大きな領域を持ち回る」関数呼び出しが「何回も行われる」場合、ポインタを使う場合と、使わない場合とでは、処理速度が大きく違ってくる……これは、「ポインタを使うと速い」と言われる理由の1つです。


5.局面その2「そのモノ自体を動かしたくない!」

int a[ 100 ]; という配列があったとして、その内容を昇順に並べ替える(ソート(sort)、ソーティング(sorting))プログラムというものを、C を学ぶ人は、1度は見たり作ったりする事でしょう。

ソートでは、配列要素を比較する動作と、入れ替える動作が必要になりますが、要素の型が int であれば、入れ替えは単純に代入演算子 = で済みます。

では、これが int a[ 100 ] ではなくて、下記のようだったら、どうやって入れ替えれば良いでしょうか。

  1. struct mydata { /* 構造体 mydata */ 
  2. char c; /* メンバ c */ 
  3. int n; /* メンバ n */ 
  4. float f[ 100 ]; /* メンバ f[ ] */ 
  5. }; 
  6. struct mydata a[ 100 ]; /* これがソート対象 */ 

「代入先 = 代入元 とせずに、memcpy( 代入先, 代入元, sizeof( struct mydata ) ) で入れ替える」。

……正解です。 正攻法です。

しかし、もし struct mydata が巨大であれば、正攻法では処理時間が長くなってしまいます。 また、処理時間の見積もりも、struct mydata の大きさに依存して変わってきます。

こういう場合にも、ポインタの出番です。

まずは、struct mydata a[ 100 ] の他に、ポインタの配列 struct mydata * ap[ 100 ] を用意します。 そして、ソートする前に、あらかじめ ap の各要素に a の各要素の先頭アドレスを設定しておきます。

  1. int i; /* あらかじめ宣言しておく */ 
  2.  
  3. /* ソートの前準備 */ 
  4. for ( i = 0; i < 100; i ++ ) { 
  5. ap[ i ] = &a[ i ]; /* a[ i ] の先頭アドレスが ap[ i ] に格納される */ 
  6. } 

比較は、a[ ~ ] 同士を直で比較しても良いですし、*ap[ ~ ] で a[ ~ ] の内容値を参照して行っても構いません。

そして「入れ替え」は、memcpy( ) を使わず、下記のように行います。

  1. struct mydata * workp; /* あらかじめ宣言しておく */ 
  2.  
  3. /* 入れ替え開始 */ 
  4. workp = ap[ 入替先 ]; 
  5. ap[ 入替先 ] = ap[ 入替元 ]; 
  6. ap[ 入替元 ] = workp; 
  7. /* 入れ替え終了 */ 

この場合、入れ替え1回で発生する代入動作は、workp への代入、ap 同士の代入、workp からの代入、この3つです(32ビットシステムでは、大抵 12バイト分のコピーに過ぎません)。 そして、対象の構造体が、どんなに大きくても、このコピー量は一定です(ここが大事です)。

ソートが終わって結果を表示する時には、ap[ ~ ] に * を付けて「参照」したものを使います。

  1. for ( i = 0; i < 100; i ++ ) { 
  2. printf( "%d\n", ( *ap[ i ] ).n ); /* メンバ n を表示してみる */ 
  3. } 

これは、「指し示すもの(ポインタ)」だけを入れ替えて、「指し示される対象(実データ)」は全く動かさないという、C では定番のテクニックです。 こうすれば、対象データの量にかかわらず、入れ替えの処理速度が一定かつ高速になります。 これも、「ポインタを使うと速い」と言われる理由の 1つです。

ちなみに、上記の printf( ) は printf( "%d\n", ap[ i ] -> n ); とも書けます。 -> はアロー演算子と呼ばれ、左辺がポインタの時のみ使える、構造体メンバを指定するための演算子です。

α->β は ( *α ).β と等価であり、構文糖(syntax sugar)に過ぎませんが、アロー演算子を使うと、左辺がポインタである事を明示出来ます。 以下のように使い分けられています。

α->β … αはポインタ、βはメンバ

α.β … αは構造体の実体、βはメンバ


6.局面その3「複数の値を関数の引数でやりとりしたい!」

先ほど書いた通り、C では、引数付きの関数を呼び出す際、実引数(呼ぶ側の値)から仮引数(呼ばれ側の変数)へ値をコピーします。 つまり、引数の値のやりとりは、「呼び出し元→呼び出し先」の一方通行しかありません。

呼び出し先の関数から返してもらいたい値が1個だけなら return 文で返せますが、複数個を返したい場合、これでは困ってしまいます。

ここでも、ポインタの出番となります。

複数の値を関数間でやりとりしたい、しかもそれらを引数にしたい、という場合は、呼ばれ側の関数の仮引数をポインタ型にしておきます。

呼ぶ側では、やりとりしたい対象のアドレスを実引数として渡します。 そのアドレス値は、呼ばれ側の仮引数――ポインタ型です――にコピーされます。

呼び出された側の関数では、何らかの結果を、その仮引数変数(ポインタですのでアドレス値が入っています)を「参照」した場所に設定します( * 演算子を使う)。

この「参照」した場所とは、もちろん、呼ぶ側の関数で用意された「対象」が置かれているメモリ領域です。

こうすれば、戻り値が2つあろうが3つあろうが、いくらでも、好きな個数だけ、やりとりする事が出来ます。

時系列で書くと、以下のような動作になります。

・呼ぶ側で、結果の欲しい対象領域を用意する(いくつでも良い)。

・そのアドレスを取得して実引数とし、目的の関数を呼び出す。

・そのアドレスが、呼ばれ側の関数の仮引数にコピーされる。

・呼ばれ側の関数では、仮引数を * で参照したところに結果を設定する。

(まさにこの時、呼び出し元の関数の「結果の欲しい対象領域」の内容が書き換わる)

・呼ばれ側の関数が終了し、呼び出し元の関数に戻る。

・すでに「欲しい結果値」は得られている。

これも C でよく使われる定番テクニックですが、呼ぶ側で構造体を用意しておいて、そのポインタを引数として渡す、というテクニックもしばしば使われます。 構造体の中には多くの内容を詰め込めるので、実用的なテクニックと言えます(実装は局面その1「そのモノ自体を持ち回りたくない!」と同様ですが、目的が違うわけです)。 このテクニックをさらに突き詰めて発展させると、クラスベースのオブジェクト指向に近いものになります。


7.応用局面「大きさがよく分からないモノを扱いたい!」

「何らかの実体を扱うにおいて、その実体に触れず、その先頭アドレスを扱う」ポインタ……その重要な「使いどころ」の1つが、これです。

たとえば、ネットワーク通信で、外部からいくつものパケットを受信するプログラムを作る、とします。 そのパケットは、固定長のヘッダと、可変長の内容から成っていて、ヘッダには内容の長さが含まれているとしましょう。 また、通信相手は、同時に最大 1000カ所まではさばける仕様である、とします。

パケット内容長は、0 だったり 1 だったりする事もあれば、100 だったり1万だったりする事もあります。 こういう場合、内容を格納しておくための領域は、どう宣言すれば良いでしょうか。

  1. char packet_content[ 1000 ][ 10000 ]; 

……泥臭い実装ですが、これは正しい解です。 1万バイト以上のパケットについては、ヘッダ内の内容長をチェックして受け付けないようにすれば、危険もありません。 しかもこのコードは、一般論的に言って高速です。

しかし、この配列定義は 10MB のメモリを消費します。 パケットの多くが数百バイトだとしたら、これは壮大な無駄です。 また、「同時に最大 1000カ所」とか、「最大1万バイト」という条件が、将来的に変わったら、無駄は、さらに増えるでしょう。

メモリを無駄に使っても、簡単・高速に動かしたい、という場合は、これでも良いのですが、無駄をなるべく省きたい場合は、ポインタの出番です。

まずは、次のように宣言します。

  1. char * packet_content[ 1000 ]; 

これは、char型領域を指すポインタを 1000個並べただけで、パケット内容を格納する領域はどこにもありません。 とりあえず「メモ用紙の束」だけ作っておいた、という感じです。 この配列定義が消費するメモリは、たかだか数KB です。

パケットを 1つ受信したら、そのヘッダ内の内容長を見て、次のようにします。

  1. packet_content[ パケット番号 ] = malloc( 内容長 ); 

malloc( ) は、「指定されたバイト長の連続したメモリの小箱を確保して(OS から借りる)、その先頭アドレスを返してくれる」関数です。 stdlib.h の中にあります。 こういう動作を、「動的メモリ確保(dynamic memory allocation)」などと言います。

こうすれば、パケット毎に、異なる長さのメモリを、弾力的に確保出来るので、無駄を非常に少なく出来ます。

確保された領域の(0から数えて)3バイト目にアクセスするには、次のように書きます。

  1. *( packet_content[ パケット番号 ] + 3 ) = 'A'; /* 'A' を書き込んでみる */ 

下記のように書いても同じです。

  1. packet_content[ パケット番号 ][ 3 ] = 'A'; 

まともに [ 10000 ] と確保するのに比べて、malloc( ) するのは実行時間もかかりますし、プログラムも若干複雑になりますので、「実行時間コストとメモリ消費コストはバーター(引き換え)である」と言えます。

メモリコストを重視する場合は、実行時間コストが多少かかっても、動的(dynamic)な処理を選ぶ事になりますが、その場合、ポインタは欠かせません。


8.他の言語では……

ポインタの存在が見えない他言語においても、「実体を動かさず『参照』する」のは、実行時間やメモリを大きく節約できる、魅力的な機能のはずです。

実際、C より新しい言語は、ほとんどが「ポインタを実装」しています。 ただ、文法的に、それを表に出さないようにしているだけです。

たとえば Java では、必要なオブジェクトを new で作り出しますが、作り出されたオブジェクトは、内部的には「ポインタで持ち回る」形で扱われています。

  1. JPanel myPanel; // myPanel の正体は C で言うところのポインタ。C っぽく書くと、struct JPanel * myPanel; 
  2. myPanel = new JPanel( ); // new は「新しく割り当てた領域の先頭アドレス」を返す。C の malloc( ) にプラスアルファしたものと考えてよい。 
  3. myPanel.setLayout( ~ ); // C っぽく書くと、(*myPanel).setLayout( ~ ); 

他の言語でも、クラスや構造体、配列など、「ある程度大きなモノ」に付けられた「名前」は、内部的にはポインタである、という事は、よくあります。

こういう言語では、「わざわざ * を付けて、参照である事を明示」しなくても、名前を書くだけで「参照になる」のですが、その代わり、ポインタそのものを「ポインタとして扱う」ことは出来ないわけです。 C は、その辺りを省略せず、細かく書く言語なのです。


9.おわりに

C は、それなりに古く、今から見ると非力なコンピュータのために作られた言語です。 C が生まれた頃、複雑かつ実用的な速度で動くプログラム(たとえば OS)は、アセンブリ言語で作られていました。

そうしたプログラムを、なんとか高級言語で書きたい、生産性の良い、読みやすいプログラムを作りたい、という欲求が有ったのは当然でした。 しかし当時のコンピュータの性能で、「アセンブリ言語に比肩し得るプログラムを書ける高級言語」を考えれば、ポインタを実装せざるを得なかったのでしょう。

ポインタはアセンブリ言語っぽい概念なので、C は「高級アセンブラ」と揶揄される事もありますが、他の言語にはない、独特のテイストやバランスを生み出してもいます。 そのテイスト、バランスは、結果論として得られたものかも知れませんが、そのテイストとバランスゆえに、C は多くのプログラマに愛用され、今も生き延びている言語になったのだと私は思います。

北川権現さんのプロフィール写真

Cは高級言語にしては珍しく、ハードウェアを直接触れるからです。例えばソフトウェアでハードウェアを操作するときには、特定のアドレスに命令を表す特定のデータを書き込むと言う操作をします。これがソフトとハードの境目なのですが、多くの高級言語では「開発者にハードウェアを意識させないのが良い」という思想なので、ハードウェアのアドレスを指定できないようになっています。

ところがCの場合はポインタ変数を使って任意のアドレスに直接書き込めるのです。この機能を使わなければハードウェアが操作できないので、他の言語では実装できないのです。

一応これはC++でもできます。しかしC++が使える組み込みエンジニアは多くありません。

それは組み込み系のエンジニアはもともとはハード屋さん出身の人も多く、ソフトウェアの知識は限定的なことが多いからです。

特にオブジェクト指向やらデザインパターンやらのソフトウェア工学の話は何回聞いてもさっぱりという組み込みエンジニアは多く、そのような技法を現場に持ち込むと黒船扱いされます。

私はそのようなハード屋に近いプログラマーと仕事をしたことがありますが、継承とかポリモーフィズムのような実際のハードウェアの動きをソフト的に抽象化されるとさっぱり理解できないようでした。

Shiro Kawaiさんのプロフィール写真

ベテランの方はCは簡単とおっしゃいますが、最近また難しくなってきたんですよ。オプティマイザが言語仕様ギリギリを攻めるようになってきたんで、「ポインタなんてメモリのアドレスでしょ」という認識だとハマります。

初心者が基礎的な範囲内で使ってる分には滅多に落とし穴は踏まないと思うんですが、中級者になってそこを踏んでしまうと、なぜ動かないかのを理解するのに分厚い言語仕様を調べるハメになるんで、スパルタンに鍛えられるという意味では難しいと言えるかと。


ごく最近もこういう事例に当たりました。20年以上広く使われてきたOSSのライブラリが、新しいコンパイラで以前と異なる結果を返すようになって調査したんです。原因はここでした。

  1. void *buf56 = &context->s256.buffer[56]; 
  2. *(sha_word64*)buf56 = context->s256.bitcount; 
  3.  
  4. /* Final transform: */ 
  5. SHA256_Internal_Transform(context, (sha_word32*)context->s256.buffer); 

context->s256.bitcountは64ビットワードで、最初の2行はそれをバッファの56オクテット目からネイティブバイトオーダーで格納する、というのを意図して

ベテランの方はCは簡単とおっしゃいますが、最近また難しくなってきたんですよ。オプティマイザが言語仕様ギリギリを攻めるようになってきたんで、「ポインタなんてメモリのアドレスでしょ」という認識だとハマります。

初心者が基礎的な範囲内で使ってる分には滅多に落とし穴は踏まないと思うんですが、中級者になってそこを踏んでしまうと、なぜ動かないかのを理解するのに分厚い言語仕様を調べるハメになるんで、スパルタンに鍛えられるという意味では難しいと言えるかと。


ごく最近もこういう事例に当たりました。20年以上広く使われてきたOSSのライブラリが、新しいコンパイラで以前と異なる結果を返すようになって調査したんです。原因はここでした。

  1. void *buf56 = &context->s256.buffer[56]; 
  2. *(sha_word64*)buf56 = context->s256.bitcount; 
  3.  
  4. /* Final transform: */ 
  5. SHA256_Internal_Transform(context, (sha_word32*)context->s256.buffer); 

context->s256.bitcountは64ビットワードで、最初の2行はそれをバッファの56オクテット目からネイティブバイトオーダーで格納する、というのを意図してます。その後バッファを別の関数に渡しています。

最近のコンパイラ使ってる方はもうピンと来たと思いますが。gcc9だと、「bitcountをバッファに格納する」というインストラクションが出力されません。無かったことになります。

なお、SHA256_Internal_Transformの呼び出しの直前に別の関数呼び出しを入れると、「bitcountをバッファに格納する」というインストラクションが出力されるようになります。

この挙動を説明するには、strict aliasing ruleとオプティマイザの気持ちを説明しなければならず、中級者になったばかりくらいの人にそれを説明するのはちと大変だなあと思うわけです。

A1aさんのプロフィール写真

私見です。

私がいつも(特に私が若い頃に当時高齢者層のおっさんに)説明してたのは、

C(C++も)はミッション車です。ATのようには運転できません、です。

もう少し説明する場合は、

最近の高級言語って、AT車みたくハンドル・アクセル・ブレーキの操作が分かれば動かせます。
なんなら衝突防止とかSBSとか、多少手荒な事をしても自動でやってくれる装置がいっぱい使える車もあります。

が、
Cは低級言語と云われてて、シフト・クラッチと同じく、車が進む仕掛けに近い部分を理解して、扱えないと(作れないし)動きません。なんならそっちの方が大事で、面倒で、解ってないと車壊すか事故起こすのも同じ。

その代わり、AT車では出来ないような事が出来ます。ダブルクラッチとかLOWに入れてエンブレ利かすとか。
でも、うまく加減してやれないと、かぶってエンストやら、使いすぎたクラッチが滑ってすっぽ抜けてとか、そういう事が起こります。そういう加減がシロウトには難しい言語です。
ただ、勝手にギア比が変わって、、みたいな想定外もない。(想定外が起こったけど解決出来ない、のはシステム的にはやっかいです)

と云ってました。

ただ、これってAT免許なんてなかった時代に免許取った(つまり今8t限定中型免許)の世代にはけっこう有効で、ついでにダブルクラッチやら昔ばなしに花が咲いておっさん客と仲良くなれるし、しかもそれを得意になって社内で話してく

私見です。

私がいつも(特に私が若い頃に当時高齢者層のおっさんに)説明してたのは、

C(C++も)はミッション車です。ATのようには運転できません、です。

もう少し説明する場合は、

最近の高級言語って、AT車みたくハンドル・アクセル・ブレーキの操作が分かれば動かせます。
なんなら衝突防止とかSBSとか、多少手荒な事をしても自動でやってくれる装置がいっぱい使える車もあります。

が、
Cは低級言語と云われてて、シフト・クラッチと同じく、車が進む仕掛けに近い部分を理解して、扱えないと(作れないし)動きません。なんならそっちの方が大事で、面倒で、解ってないと車壊すか事故起こすのも同じ。

その代わり、AT車では出来ないような事が出来ます。ダブルクラッチとかLOWに入れてエンブレ利かすとか。
でも、うまく加減してやれないと、かぶってエンストやら、使いすぎたクラッチが滑ってすっぽ抜けてとか、そういう事が起こります。そういう加減がシロウトには難しい言語です。
ただ、勝手にギア比が変わって、、みたいな想定外もない。(想定外が起こったけど解決出来ない、のはシステム的にはやっかいです)

と云ってました。

ただ、これってAT免許なんてなかった時代に免許取った(つまり今8t限定中型免許)の世代にはけっこう有効で、ついでにダブルクラッチやら昔ばなしに花が咲いておっさん客と仲良くなれるし、しかもそれを得意になって社内で話してくれたりして、こっちペースで廻せるきっかけになるいい手だったんですが、

若い子には解らないみたいです。

今なら、何に例えればいいんでしょうね、、

Miyata Akiraさんのプロフィール写真

セミコロンは文と文を区切る区切子(デリミタ)ではなく、ましてや、単なる飾りでもありません。C言語において構文上の重要な役割を持っています。

セミコロンは文の一種である式文(expression statement)を構成する終端記号です。もしセミコロンがなければ文とはならず、(expression)として扱われます。

C言語が登場した1970年代当時、他の言語と一線を画した機能の一つに代入式があります。代入式はALGOLが発祥ですが、商用で成功したプログラミング言語ではC言語をもって嚆矢とします。当時主流だった多くのプログラミング言語には代入文はあっても代入式はありませんでした。この二つは似て非なるもので、文法上の位置づけが異なります。

例えばC言語で y = x * 0.5 と書けば、これはセミコロンで終わっていないので代入文ではなく、代入式と呼ばれる式の一種でしかありません。代入式は「左辺式 = 右辺式」という形をしており、

  • 左辺式が指し示すオブジェクトに、右辺式の値を代入するとともに
  • そのオブジェクトの代入後の値を代入式自身の値とする

という働きをします。y = x * 0.5 の例では、仮に変数 x の値が 3.0 だとすれば、右辺の乗算式 x * 0.5 の計算結果すなわち 1.5 が変数 y に代入され、さらに、代入後の変数 y の値すなわち 1.5 が代入式 y = x * 0

セミコロンは文と文を区切る区切子(デリミタ)ではなく、ましてや、単なる飾りでもありません。C言語において構文上の重要な役割を持っています。

セミコロンは文の一種である式文(expression statement)を構成する終端記号です。もしセミコロンがなければ文とはならず、(expression)として扱われます。

C言語が登場した1970年代当時、他の言語と一線を画した機能の一つに代入式があります。代入式はALGOLが発祥ですが、商用で成功したプログラミング言語ではC言語をもって嚆矢とします。当時主流だった多くのプログラミング言語には代入文はあっても代入式はありませんでした。この二つは似て非なるもので、文法上の位置づけが異なります。

例えばC言語で y = x * 0.5 と書けば、これはセミコロンで終わっていないので代入文ではなく、代入式と呼ばれる式の一種でしかありません。代入式は「左辺式 = 右辺式」という形をしており、

  • 左辺式が指し示すオブジェクトに、右辺式の値を代入するとともに
  • そのオブジェクトの代入後の値を代入式自身の値とする

という働きをします。y = x * 0.5 の例では、仮に変数 x の値が 3.0 だとすれば、右辺の乗算式 x * 0.5 の計算結果すなわち 1.5 が変数 y に代入され、さらに、代入後の変数 y の値すなわち 1.5 が代入式 y = x * 0.5 の値となります。

これの何がスゴイかというと、代入式は式の一種なので別の式の中に代入式を含ませることができるのです。例えば z = y = x = 1.0 という式が書けるのです。これは、括弧を使って演算子の優先順位を分かりやすく示せば、 z = (y = (x = 1.0)) と書いたのと同じです。x = ~、y = ~、z = ~ という3つの代入式が入れ子になっており、いずれの代入式の値も 1.0 で、結果として、x, y, z のそれぞれに 1.0 を代入したのと等価になります。

さらには、sqrt(a = (b = 6.0) + 10.0) なんて式も書けます。これは、変数 b に 6.0 を代入し、6.0 + 10.0 を計算して変数 a に 16.0 を代入し、さらに sqrt(16.0) を計算して 4.0 を返す式です。FORTRANやBASICではこんな芸当はとてもできません。最近流行りの Python ですら代入式が導入されたのは最近(バージョン3.8)からであり、しかも代入式が使える箇所は限定的です。

このように、C言語ではそれまでの式の概念を大幅に拡張しました。そして、任意の式の末尾にセミコロンを付けた構文「式 ; 」を式文と定義し、文の一種としました。プログラム上の意味があるかどうかは別として、以下の各行はいずれも式文です。

  1. a = 3.0; 
  2. 1.0 + 4.0; 
  3. b; 
  4. b + c; 
  5. sqrt(x); 
金野 祥久さんのプロフィール写真

30年以上前の自分を思い出しました.懐かしいなー.同じようなことを考えて,当時住んでいたアパートにほど近い,南阿佐ヶ谷駅の横にあった本屋に行き,Cのソースコードがいちばんたくさん掲載されている本を買うことにしました.そこで出会ったのがMINIXオペレーティング・システムです.

MINIXオペレーティング・システム (アスキーブックス)
MINIXオペレーティング・システム (アスキーブックス)

この本でUNIX文化の一端に触れたことは,その後の自分に大きな影響を与えました.UNIXやLinuxをある程度知っているふりができるのもこの本と,この本をきっかけとしてその後手にすることになる関連本のおかげです.あの本屋はもうなくなってしまったようですが,当時あの狭くてごちゃごちゃした本屋でこの本にばったり出会ったことは,思い返してみると幸運なことでした.

というわけで,「このあとどうすればいいですか?」に対する私なりの答えは「本屋さんに行ってみよう」です.

ただ残念なことに,いま本屋に行ってC言語やそれに関連した本を探しても,ソースコードが何10ページにも渡って掲載されているような本は見つからないでしょう.…えっ,ああ,そうです.上のMINIX本にはMINIXのソースコードがすべて掲載されていたので,ほんとうに何10ページもCのプログラムが載っていたわけです.私がこの本を買ったのは1989年のことですが,当時のパソコン関連本はいまほど人口に膾炙

30年以上前の自分を思い出しました.懐かしいなー.同じようなことを考えて,当時住んでいたアパートにほど近い,南阿佐ヶ谷駅の横にあった本屋に行き,Cのソースコードがいちばんたくさん掲載されている本を買うことにしました.そこで出会ったのがMINIXオペレーティング・システムです.

MINIXオペレーティング・システム (アスキーブックス)
MINIXオペレーティング・システム (アスキーブックス)

この本でUNIX文化の一端に触れたことは,その後の自分に大きな影響を与えました.UNIXやLinuxをある程度知っているふりができるのもこの本と,この本をきっかけとしてその後手にすることになる関連本のおかげです.あの本屋はもうなくなってしまったようですが,当時あの狭くてごちゃごちゃした本屋でこの本にばったり出会ったことは,思い返してみると幸運なことでした.

というわけで,「このあとどうすればいいですか?」に対する私なりの答えは「本屋さんに行ってみよう」です.

ただ残念なことに,いま本屋に行ってC言語やそれに関連した本を探しても,ソースコードが何10ページにも渡って掲載されているような本は見つからないでしょう.…えっ,ああ,そうです.上のMINIX本にはMINIXのソースコードがすべて掲載されていたので,ほんとうに何10ページもCのプログラムが載っていたわけです.私がこの本を買ったのは1989年のことですが,当時のパソコン関連本はいまほど人口に膾炙していませんでしたから,尖った本が多かったのです.いまはそういう本が少なくて物足りない気がします.

ですので,専門書をたくさん置いてある大型書店に行くのがよいかと思います.あるいはちょっと古めの洋書を探すとか.尖っている本に出会ってほしいと思います.

参考になれば幸いです.

松本 光一さんのプロフィール写真

偉くないです。今なら Kotlin とか、Python とかが偉いかも知れません。あと、Javascript/Typescript がしぶとく粘って、その隙を Go と Rust が窺ってるって感じ。あと、何気に C# が存在感を放ってますね。C? ああ、あのロートル?

ただし、組み込みとか OS 開発とかの現場ではヒーローです。他の言語は相手になりません。C++ が必死になって対抗していますが、すぐバイナリを肥大化させるので、C に太刀打ちできてません。Objective-C? 残念ながら macOS のカーネルって、C なんですよ。I/O Kit だけは C++ です。

そういえば、Windows が C++ を使って開発されていますが、カーネルが巨大化してる上にサブシステムも巨大です。組み込み機器にはそんな巨大な空間はないのです。一時期 Microsoft はやっきになって組み込み向け Windows を宣伝してましたが、撤退しました。

Unix/Linux カーネルは C ですが、それでも結構なサイズなので厳しい世界です。往事の XFree86 とか、X.Org も C でしたね。それで無理くりオブジェクト指向もどきを実装したのでコードが大変複雑でした。Linus が F○ck! とか言いかねないレベルです。

まあ向き不向きがあるってことで。

システム系も Web 系もごったまぜで比

偉くないです。今なら Kotlin とか、Python とかが偉いかも知れません。あと、Javascript/Typescript がしぶとく粘って、その隙を Go と Rust が窺ってるって感じ。あと、何気に C# が存在感を放ってますね。C? ああ、あのロートル?

ただし、組み込みとか OS 開発とかの現場ではヒーローです。他の言語は相手になりません。C++ が必死になって対抗していますが、すぐバイナリを肥大化させるので、C に太刀打ちできてません。Objective-C? 残念ながら macOS のカーネルって、C なんですよ。I/O Kit だけは C++ です。

そういえば、Windows が C++ を使って開発されていますが、カーネルが巨大化してる上にサブシステムも巨大です。組み込み機器にはそんな巨大な空間はないのです。一時期 Microsoft はやっきになって組み込み向け Windows を宣伝してましたが、撤退しました。

Unix/Linux カーネルは C ですが、それでも結構なサイズなので厳しい世界です。往事の XFree86 とか、X.Org も C でしたね。それで無理くりオブジェクト指向もどきを実装したのでコードが大変複雑でした。Linus が F○ck! とか言いかねないレベルです。

まあ向き不向きがあるってことで。

システム系も Web 系もごったまぜで比較したら以下のような結果になるようです。

プログラミング言語の人気ランキング、順位変動は縮小傾向にある――RedMonkが調査
RedMonkが発表した2022年第1四半期のプログラミング言語ランキングによると、JavaScript、Python、Javaがトップ3を占めた。KotlinやRustの勢いにDartが追随していることも分かった。
Quora Userさんのプロフィール写真

他の言語にはあまりない概念だからです。

  1. int *x; 

と書いても実際にint型の値をもった変数が作られるわけではないというのは他言語を先に勉強した人からすると混乱すると思います。また、

  1. int x; 
  2. int *y = (int *)malloc(sizeof(int)); 

の違いも混乱するでしょう。大抵の言語では基本的にプリミティブ型以外の構造体などので変数は関数に参照渡しで渡されますが、Cではポインタを使わない限りは値渡しで関数内ではコピーになるため、関数内でメンバ変数の値を変えてもそれが元の構造体変数には反映されないという点も始めは難しいのではないでしょうか。

次に配列とポインタの関係が挙げられます。

  1. int x[10]; 

とした時、xというシンボルは配列の先頭のアドレスを指すポインタとか言われても、データがメモリ上に連続で配置されているというようなハードウェアに近い知識がないと最初は意味がわかりません。

  1. int x[10]; 
  2. int *y = (int *)malloc(sizeof(int)*10); 

のように同じようで微妙に違うのも困ったもんです。(前者はstack,後者はheap)

ポインタのポインタで2次元配列を扱えるけどイコールではないのも数値計算をやっていた自分は始め困惑していました。

あとはポインタのキャストもなんで必要なのか初学者が理解するのは難しいように思えます。

要はポインタは自由

他の言語にはあまりない概念だからです。

  1. int *x; 

と書いても実際にint型の値をもった変数が作られるわけではないというのは他言語を先に勉強した人からすると混乱すると思います。また、

  1. int x; 
  2. int *y = (int *)malloc(sizeof(int)); 

の違いも混乱するでしょう。大抵の言語では基本的にプリミティブ型以外の構造体などので変数は関数に参照渡しで渡されますが、Cではポインタを使わない限りは値渡しで関数内ではコピーになるため、関数内でメンバ変数の値を変えてもそれが元の構造体変数には反映されないという点も始めは難しいのではないでしょうか。

次に配列とポインタの関係が挙げられます。

  1. int x[10]; 

とした時、xというシンボルは配列の先頭のアドレスを指すポインタとか言われても、データがメモリ上に連続で配置されているというようなハードウェアに近い知識がないと最初は意味がわかりません。

  1. int x[10]; 
  2. int *y = (int *)malloc(sizeof(int)*10); 

のように同じようで微妙に違うのも困ったもんです。(前者はstack,後者はheap)

ポインタのポインタで2次元配列を扱えるけどイコールではないのも数値計算をやっていた自分は始め困惑していました。

あとはポインタのキャストもなんで必要なのか初学者が理解するのは難しいように思えます。

要はポインタは自由度が高すぎるんですよ。C++の参照くらいでちょうどいいし、実際殆どのポインタの用途はそれのはず。

あ、もう一つありますね。NULLのポインタ指している値を参照しようとするとJavaのnull pointer exceptionと同じように落ちます。ここまではいいのですが、でたらめなアドレスを指しているポインタの値を参照しても落ちないし、そのメモリの値を変えることすらできます。なのでポインタを使う場合はしつこく初期化やnullチェックに気をつけないといけません。

Hantani Sadahikoさんのプロフィール写真

Arduino(アルディーノ)をお勧めします。

小型マイコンボードです。USBでパソコンと繋ぐだけで使えます。開発環境も無料です。色々なセンサーや無線モジュール乗っけると今流行りのIoT、組込インターネットデバイスが作れます。

Arduino IDEのインストールと設定 (Windows, Mac, Linux対応)
Arduino IDEは世界的にも有名なマイコンボード用開発環境の一つで、初心者やプログラミング学習、小規模システム開発に適しています。本記事では、Arduino IDEやマイコンボードとは何か?といった点から、実際にインストールして使う手順を分かりやすく解説しました。Windows, Mac, Linuxなど、複数のOSでの手順も載せております。 更新日 : 2024年9月28日 Arduino IDEとは何か? Arduino IDEとは Arduinoなどのマイコンボード用のプログラムを開発するためのPC(パソコン)用のソフト(アプリ)のこと です。 まずマイコンボードについて説明しておきましょう。マイコンボードとはその名の通り、マイコン(マイクロコントローラー)を搭載した基板やデバイスのことです。マイコンにユーザーが作成したプログラムを書き込むことで、様々な動作をさせることができます。例えば、ディスプレイに文字を表示させたり、温度を測定したり、インターネットと通信したり、といった具合です。(もちろん必要なディスプレイやセンサーなどが接続されている必要はありますが・・) つまり、 「こんな機能をもつデバイスを作れないかな?」というユーザーの希望を実現できるのがマイコンボード なのです。 マイコンボードの1つ「Arduino UNO」 ではマイコン用のプログラムはどのようにして作ればよいのでしょうか?プログラムの作成はPCで行い、以下のような手順になります。 プログラミング言語を使ってプログラムを記述する プログラムをマイコンが理解できる形式に変換する(コンパイル) プログラムをマイコンに書き込む これらのステップを行ってくれるソフトは「開発環境」などと呼ばれています。マイコンボードや開発環境は様々なものがあります。また、対応関係もあり、ある開発環境が対応しているマイコンボードというものが決まっています。この中で Arduinoマイコンボード用の公式の開発環境がArduino IDE なのです。 なぜArduino IDEなのか? 実際の所、どの開発環境にもメリット、デメリットはあるのですが、Arduino IDE(とその対応マイコンボード)は 初心者やプログラミング学習、小規模なプログラム開発には良い選択肢 です。その理由を説明します。 シンプルな構成 こちらはArduino IDEでLEDを点滅させる簡単なプログラム(Arduinoではスケッチと呼びます)を開いた画面です。 まず画面構成が 必須の機能のみに絞り込んでいて、非常にシンプル です。初心者にとって、開発環境のセットアップのハードルは意外と高いものです。説明通りに作業してみたものの、エラーがでてコンパイルに失敗したり、マイコンボードの書き込みがうまく行かずにつまづいてしまう、というケースはよくあります。Arduino IDEであればインストール後、最低限の設定だけで動作させることができます。使用ボードにもよりますが、接続もUSBでつなぐだけです。 また、先程の画面のプログラムコードはわずか10行です。LEDを点滅させるだけならそんなものじゃないか?と思うかもしれませんが、開発環境によってはマイコンを動作させる「下準備」のコードやファイルを用意しないといけない場合もあるのです。Arduino IDEだと こういった準備は自動で行ってくれる ため、コードもシンプルになり、このサンプルはファイルも1つだけです。 情報が豊富 Arduino UNOをはじめとするArduinoシリーズは 世界的に最も有名なマイコンボードの1つです 。そのため、なにか問題が起きても、大抵の場合原因や解決策を見つけることができます。また、世界中の開発者がArduino IDEで動作するプログラムを「ライブラリ」として公開しています。例えば温度センサーを使いたい場合、対応したライブラリがあれば自分で細かいプログラムを書かずとも動作させることができます。もちろん、学習のために自分でプログラムを書くという選択肢もあります。 Arduinoシリーズ以外のマイコンボードにも対応 Arduino IDEには「ボードマネージャ」という機能があり、公式にサポートしているマイコンボード以外にも 対応ボードを拡張 できます。Arduino IDEが普及しているため、多くのマイコンボードが対応しています。例えばWiFi内蔵マイコンのESP32や、Raspberry Pi財団のRaspberry Pi PicoもArduino IDEで動作可能です。互換性が高くなるような仕組みになっているので、こういった異なるマイコンボードにおいても既存のコードやライブラリの大半がそのまま動作します。 当Indoor Corgiの以下の製品もArduino IDEで開発することで、自由に動作をカスタマイズできます。 Arduino IDEで開発できる「ESP-IR+TPH Monitor」 Arduino IDEのバージョンについて Arduino IDEは 最新バージョン2.xとレガシーバージョンの1.8があります 。 基本的に最新バージョンで問題ありませんが、古いスケッチはレガシーバージョン1.8でないとコンパイルに失敗する場合があります。当サイトで配布しているサンプルについては、レガシーバージョン1.8が必要な場合は明記しています。 レガシーバージョン1.8のインストール方法は こちら を参照してください。 ダウンロード 少し前置きが長くなってしまいましたが、まずは Arduino公式サイトのソフトウェアページ からダウンロードしましょう。ページ上部の「Downloads」から、自分のOSにあったものを選択します。 比較的最近のMacの場合は「macOS Apple Sillicon…」で問題ありませんが、intel製CPU搭載のMacの場合は「macOS Intel…」をダウンロードします。 寄付とメールマガジン登録の画面が出るので、不要な場合はいずれも「JUST DOWNLOAD」をクリックしてダウンロードします。 これ以降、OSごとの解説になりますが、動作確認した環境は以下の通りです。 Windows : Windows11 Mac : macOS Ventura 13.1 Linux : Ubuntu22.04 インストール (Windows) ダウンロードしたexeファイルをダブルクリックするとインストーラーが起動するので、手順に従ってインストールしましょう。「同意する」、「次へ」、「インストール」と順にクリックしていくだけでOKです。 インストーラーに従って進めればOK インストール完了後は自動的にArduino IDEが起動します。初回起動時は、USBドライバーなどのダウンロード、インストールが自動で行われるので、許可するようにしてください。 インストール (Mac) ダウンロードしたdmgファイルをダブルクリックすると以下のようなダイアログボックスが開きます。一般的なアプリと同様、Arduino IDEアイコンを右のApplicationsにドラッグ&ドロップすればイ

開発言語は「C言語」です。以下のリファレンスが読めればOKです。

Arduino日本語リファレンス
Arduino言語 Arduino言語はC/C++をベースにしており、C言語のすべての構造と、いくつかのC++の機能をサポートしています。また、AVR Libcにリンクされていて、その関数を利用できます。 setup() loop() 制御文 if if else switch case for while do while break continue return goto 基本的な文法 ; (セミコロン) {} (波カッコ) コメント #define #include 算術演算子 + - * / % (剰余) = (代入) 比較演算子 == != < > <= >= ブール演算子 && (論理積) || (論理和) ! (否定) ビット演算子 ビット演算子は変数をビットのレベルで計算するためのものです。ビット演算子によって、広範囲なプログラミング上の問題を解決することができます。 & (AND) | (OR) ^ (XOR) ~ (NOT) << (左シフト) >> (右シフト) ポート操作 複合演算子 ++ (加算) -- (減算) += -= *= /= &= (AND) |= (OR) データ型 bool (boolean) char unsigned char byte int (整数型) unsigned int (符号なし整数型) word long (long整数型) unsigned long (符号なしlong整数型) float (浮動小数点型) double (倍精度浮動小数点型) 文字列(配列) 配列 void Stringクラス Stringクラスは文字を扱う配列型の文字列よりも複雑な連結、追加、置換、検索といった操作が可能です。そのかわり、配列型より多くのメモリを消費します。ダブルクォーテーションで囲まれた文字列定数は配列として処理されます。 String() 関数 演算子 定数 定数はプログラムのなかで値が変化しない変数と考えるといいでしょう。Arduino言語であらかじめ定義されている定数と、ユーザーが自分で定義して使う定数があります。おもにプログラムのメンテナンス性を高めるために使われます。 true/false (論理レベルを定義する定数) HIGH/LOW (ピンのレベルを定義する定数) INPUT/OUTPUT (デジタルピンを定義する定数) 整数の定数 浮動小数点数の定数 変数の応用 変数のスコープ Static volatile const PROGMEMとFマクロ Cast sizeof デジタル入出力関数 pinMode(pin, mode) digitalWrite(pin, value) digitalRead(pin) アナログ入出力関数 analogRead(pin) analogWrite(pin, value) analogReference(type) analogReadResolution(bits) analogWriteResolution(bits) その他の入出力関数 shiftOut(dataPin, clockPin, bitOrder, value) shiftIn(dataPin, clockPin, bitOrder) pulseIn(pin, value, timeout) tone(pin, frequency) noTone(pin) 時間に関する関数 millis() micros() delay(ms) delayMicroseconds(us) 数学的な関数 min()、max()、abs()、constraint()の各関数は実装の都合により、カッコ内で関数を使ったり変数を操作することができません。たとえば、min(a++, 100)とすると正しい答が得られません。かわりに、次のようにしてください。 a++; min(a, 100); min(x, y) max(x, y) abs(x) constrain(x, a, b) map(value, fromLow, fromHigh, toLow, toHigh) pow(base, exponent) sqrt(x) 三角関数 sin(rad) cos(rad) tan(rad) 乱数に関する関数 randomSeed(seed) random(min, max) ビットとバイトの操作 lowByte() highByte() bitRead() bitWrite() bitSet() bitClear() bit() 外部割り込み attachInterrupt(interrupt, function, mode) detachInterrupt(interrupt) 割り込み interrupts() noInterrupts() シリアル通信 他のコンピュータやデバイスと通信するために、どのボードにも最低1つのシリアルポートが用意されています。Arduino Unoではピン0と1がシリアルポートのピンで、この2ピンを通信に使用している間は、デジタル入出力として使うことはできません。 Arduino IDEはシリアルモニタを備えていて、Arduinoボードが送信したメッセージを表示したり、逆にIDEからボードへメッセージを送信することができます。 Serial.begin(speed) Serial.end() Serial.available() Serial.read() Serial.peek() Serial.flush() Serial.print(data, format) Serial.println(data, format) Serial.write(val) Serial.readString() 標準ライブラリ Arduinoのライブラリは、あらかじめIDEに付属する標準のライブラリと、コミュニティーのメンバーから寄稿されたライブラリの2種類があります。寄稿されたものはユーザーが個々にダウンロードしてインストールする必要があります。 ライブラリの使い方 EEPROM Arduinoボードが搭載するマイクロコントローラはEEPROMと呼ばれるメモリを持っています。EEPROMは(まるで小さなハードディスクのように)電源を切っても内容が消えません。その容量は機種によって異なり、Arduino UnoのATmega328Pは1024バイト、Nano Everyは256バイトです。 MKRファミリーなどのSAMDチップはEEPROMを内蔵していないため、不揮発性の記憶エリアを使いたい場合は別の方法が必要です。 このライブラリはEEPROMに対する書き込みと読み込みを可能にします。 EEPROM.read(address) EEPROM.write(address, value) SoftwareSerial ソフトウェアシリアルライブラリはArduinoボードの0〜1番以外のピンを使ってシリアル通信を行うために開発されました。本来ハードウェアで実現されている機能をソフトウェアによって複製したので、SoftwareS

Arduinoは色々種類がありますがお勧めは「MakerUNO」ですね。

秋月電子の通信販売でクレジットカード、銀行振込、代引き(商品受け取るときにお金を払う)で買えます。

https://akizukidenshi.com/catalog/g/gM-16285/

利点は以下3点です。

①安い 通常のArduinoUNOが3000円くらいなのにMakerUNOは「720円」

②最初からLEDが13個、圧電スピーカー、メカスイッチがついている。

③ArduinoUNO互換機。だからArduinoUNOのプログラムがそのまま動く。Arduioはオープンハードウェアだから色んな所が作ってます。

元々このMakerUNOはマレーシアで子供の教育用に考えて作られているので色々な機能が最初から入っています。

電源入れるとお馴染みのテーマが流れるでしょう。

Micro USBケーブルは付いてないので一緒に買って下

Arduino(アルディーノ)をお勧めします。

小型マイコンボードです。USBでパソコンと繋ぐだけで使えます。開発環境も無料です。色々なセンサーや無線モジュール乗っけると今流行りのIoT、組込インターネットデバイスが作れます。

Arduino IDEのインストールと設定 (Windows, Mac, Linux対応)
Arduino IDEは世界的にも有名なマイコンボード用開発環境の一つで、初心者やプログラミング学習、小規模システム開発に適しています。本記事では、Arduino IDEやマイコンボードとは何か?といった点から、実際にインストールして使う手順を分かりやすく解説しました。Windows, Mac, Linuxなど、複数のOSでの手順も載せております。 更新日 : 2024年9月28日 Arduino IDEとは何か? Arduino IDEとは Arduinoなどのマイコンボード用のプログラムを開発するためのPC(パソコン)用のソフト(アプリ)のこと です。 まずマイコンボードについて説明しておきましょう。マイコンボードとはその名の通り、マイコン(マイクロコントローラー)を搭載した基板やデバイスのことです。マイコンにユーザーが作成したプログラムを書き込むことで、様々な動作をさせることができます。例えば、ディスプレイに文字を表示させたり、温度を測定したり、インターネットと通信したり、といった具合です。(もちろん必要なディスプレイやセンサーなどが接続されている必要はありますが・・) つまり、 「こんな機能をもつデバイスを作れないかな?」というユーザーの希望を実現できるのがマイコンボード なのです。 マイコンボードの1つ「Arduino UNO」 ではマイコン用のプログラムはどのようにして作ればよいのでしょうか?プログラムの作成はPCで行い、以下のような手順になります。 プログラミング言語を使ってプログラムを記述する プログラムをマイコンが理解できる形式に変換する(コンパイル) プログラムをマイコンに書き込む これらのステップを行ってくれるソフトは「開発環境」などと呼ばれています。マイコンボードや開発環境は様々なものがあります。また、対応関係もあり、ある開発環境が対応しているマイコンボードというものが決まっています。この中で Arduinoマイコンボード用の公式の開発環境がArduino IDE なのです。 なぜArduino IDEなのか? 実際の所、どの開発環境にもメリット、デメリットはあるのですが、Arduino IDE(とその対応マイコンボード)は 初心者やプログラミング学習、小規模なプログラム開発には良い選択肢 です。その理由を説明します。 シンプルな構成 こちらはArduino IDEでLEDを点滅させる簡単なプログラム(Arduinoではスケッチと呼びます)を開いた画面です。 まず画面構成が 必須の機能のみに絞り込んでいて、非常にシンプル です。初心者にとって、開発環境のセットアップのハードルは意外と高いものです。説明通りに作業してみたものの、エラーがでてコンパイルに失敗したり、マイコンボードの書き込みがうまく行かずにつまづいてしまう、というケースはよくあります。Arduino IDEであればインストール後、最低限の設定だけで動作させることができます。使用ボードにもよりますが、接続もUSBでつなぐだけです。 また、先程の画面のプログラムコードはわずか10行です。LEDを点滅させるだけならそんなものじゃないか?と思うかもしれませんが、開発環境によってはマイコンを動作させる「下準備」のコードやファイルを用意しないといけない場合もあるのです。Arduino IDEだと こういった準備は自動で行ってくれる ため、コードもシンプルになり、このサンプルはファイルも1つだけです。 情報が豊富 Arduino UNOをはじめとするArduinoシリーズは 世界的に最も有名なマイコンボードの1つです 。そのため、なにか問題が起きても、大抵の場合原因や解決策を見つけることができます。また、世界中の開発者がArduino IDEで動作するプログラムを「ライブラリ」として公開しています。例えば温度センサーを使いたい場合、対応したライブラリがあれば自分で細かいプログラムを書かずとも動作させることができます。もちろん、学習のために自分でプログラムを書くという選択肢もあります。 Arduinoシリーズ以外のマイコンボードにも対応 Arduino IDEには「ボードマネージャ」という機能があり、公式にサポートしているマイコンボード以外にも 対応ボードを拡張 できます。Arduino IDEが普及しているため、多くのマイコンボードが対応しています。例えばWiFi内蔵マイコンのESP32や、Raspberry Pi財団のRaspberry Pi PicoもArduino IDEで動作可能です。互換性が高くなるような仕組みになっているので、こういった異なるマイコンボードにおいても既存のコードやライブラリの大半がそのまま動作します。 当Indoor Corgiの以下の製品もArduino IDEで開発することで、自由に動作をカスタマイズできます。 Arduino IDEで開発できる「ESP-IR+TPH Monitor」 Arduino IDEのバージョンについて Arduino IDEは 最新バージョン2.xとレガシーバージョンの1.8があります 。 基本的に最新バージョンで問題ありませんが、古いスケッチはレガシーバージョン1.8でないとコンパイルに失敗する場合があります。当サイトで配布しているサンプルについては、レガシーバージョン1.8が必要な場合は明記しています。 レガシーバージョン1.8のインストール方法は こちら を参照してください。 ダウンロード 少し前置きが長くなってしまいましたが、まずは Arduino公式サイトのソフトウェアページ からダウンロードしましょう。ページ上部の「Downloads」から、自分のOSにあったものを選択します。 比較的最近のMacの場合は「macOS Apple Sillicon…」で問題ありませんが、intel製CPU搭載のMacの場合は「macOS Intel…」をダウンロードします。 寄付とメールマガジン登録の画面が出るので、不要な場合はいずれも「JUST DOWNLOAD」をクリックしてダウンロードします。 これ以降、OSごとの解説になりますが、動作確認した環境は以下の通りです。 Windows : Windows11 Mac : macOS Ventura 13.1 Linux : Ubuntu22.04 インストール (Windows) ダウンロードしたexeファイルをダブルクリックするとインストーラーが起動するので、手順に従ってインストールしましょう。「同意する」、「次へ」、「インストール」と順にクリックしていくだけでOKです。 インストーラーに従って進めればOK インストール完了後は自動的にArduino IDEが起動します。初回起動時は、USBドライバーなどのダウンロード、インストールが自動で行われるので、許可するようにしてください。 インストール (Mac) ダウンロードしたdmgファイルをダブルクリックすると以下のようなダイアログボックスが開きます。一般的なアプリと同様、Arduino IDEアイコンを右のApplicationsにドラッグ&ドロップすればイ

開発言語は「C言語」です。以下のリファレンスが読めればOKです。

Arduino日本語リファレンス
Arduino言語 Arduino言語はC/C++をベースにしており、C言語のすべての構造と、いくつかのC++の機能をサポートしています。また、AVR Libcにリンクされていて、その関数を利用できます。 setup() loop() 制御文 if if else switch case for while do while break continue return goto 基本的な文法 ; (セミコロン) {} (波カッコ) コメント #define #include 算術演算子 + - * / % (剰余) = (代入) 比較演算子 == != < > <= >= ブール演算子 && (論理積) || (論理和) ! (否定) ビット演算子 ビット演算子は変数をビットのレベルで計算するためのものです。ビット演算子によって、広範囲なプログラミング上の問題を解決することができます。 & (AND) | (OR) ^ (XOR) ~ (NOT) << (左シフト) >> (右シフト) ポート操作 複合演算子 ++ (加算) -- (減算) += -= *= /= &= (AND) |= (OR) データ型 bool (boolean) char unsigned char byte int (整数型) unsigned int (符号なし整数型) word long (long整数型) unsigned long (符号なしlong整数型) float (浮動小数点型) double (倍精度浮動小数点型) 文字列(配列) 配列 void Stringクラス Stringクラスは文字を扱う配列型の文字列よりも複雑な連結、追加、置換、検索といった操作が可能です。そのかわり、配列型より多くのメモリを消費します。ダブルクォーテーションで囲まれた文字列定数は配列として処理されます。 String() 関数 演算子 定数 定数はプログラムのなかで値が変化しない変数と考えるといいでしょう。Arduino言語であらかじめ定義されている定数と、ユーザーが自分で定義して使う定数があります。おもにプログラムのメンテナンス性を高めるために使われます。 true/false (論理レベルを定義する定数) HIGH/LOW (ピンのレベルを定義する定数) INPUT/OUTPUT (デジタルピンを定義する定数) 整数の定数 浮動小数点数の定数 変数の応用 変数のスコープ Static volatile const PROGMEMとFマクロ Cast sizeof デジタル入出力関数 pinMode(pin, mode) digitalWrite(pin, value) digitalRead(pin) アナログ入出力関数 analogRead(pin) analogWrite(pin, value) analogReference(type) analogReadResolution(bits) analogWriteResolution(bits) その他の入出力関数 shiftOut(dataPin, clockPin, bitOrder, value) shiftIn(dataPin, clockPin, bitOrder) pulseIn(pin, value, timeout) tone(pin, frequency) noTone(pin) 時間に関する関数 millis() micros() delay(ms) delayMicroseconds(us) 数学的な関数 min()、max()、abs()、constraint()の各関数は実装の都合により、カッコ内で関数を使ったり変数を操作することができません。たとえば、min(a++, 100)とすると正しい答が得られません。かわりに、次のようにしてください。 a++; min(a, 100); min(x, y) max(x, y) abs(x) constrain(x, a, b) map(value, fromLow, fromHigh, toLow, toHigh) pow(base, exponent) sqrt(x) 三角関数 sin(rad) cos(rad) tan(rad) 乱数に関する関数 randomSeed(seed) random(min, max) ビットとバイトの操作 lowByte() highByte() bitRead() bitWrite() bitSet() bitClear() bit() 外部割り込み attachInterrupt(interrupt, function, mode) detachInterrupt(interrupt) 割り込み interrupts() noInterrupts() シリアル通信 他のコンピュータやデバイスと通信するために、どのボードにも最低1つのシリアルポートが用意されています。Arduino Unoではピン0と1がシリアルポートのピンで、この2ピンを通信に使用している間は、デジタル入出力として使うことはできません。 Arduino IDEはシリアルモニタを備えていて、Arduinoボードが送信したメッセージを表示したり、逆にIDEからボードへメッセージを送信することができます。 Serial.begin(speed) Serial.end() Serial.available() Serial.read() Serial.peek() Serial.flush() Serial.print(data, format) Serial.println(data, format) Serial.write(val) Serial.readString() 標準ライブラリ Arduinoのライブラリは、あらかじめIDEに付属する標準のライブラリと、コミュニティーのメンバーから寄稿されたライブラリの2種類があります。寄稿されたものはユーザーが個々にダウンロードしてインストールする必要があります。 ライブラリの使い方 EEPROM Arduinoボードが搭載するマイクロコントローラはEEPROMと呼ばれるメモリを持っています。EEPROMは(まるで小さなハードディスクのように)電源を切っても内容が消えません。その容量は機種によって異なり、Arduino UnoのATmega328Pは1024バイト、Nano Everyは256バイトです。 MKRファミリーなどのSAMDチップはEEPROMを内蔵していないため、不揮発性の記憶エリアを使いたい場合は別の方法が必要です。 このライブラリはEEPROMに対する書き込みと読み込みを可能にします。 EEPROM.read(address) EEPROM.write(address, value) SoftwareSerial ソフトウェアシリアルライブラリはArduinoボードの0〜1番以外のピンを使ってシリアル通信を行うために開発されました。本来ハードウェアで実現されている機能をソフトウェアによって複製したので、SoftwareS

Arduinoは色々種類がありますがお勧めは「MakerUNO」ですね。

秋月電子の通信販売でクレジットカード、銀行振込、代引き(商品受け取るときにお金を払う)で買えます。

https://akizukidenshi.com/catalog/g/gM-16285/

利点は以下3点です。

①安い 通常のArduinoUNOが3000円くらいなのにMakerUNOは「720円」

②最初からLEDが13個、圧電スピーカー、メカスイッチがついている。

③ArduinoUNO互換機。だからArduinoUNOのプログラムがそのまま動く。Arduioはオープンハードウェアだから色んな所が作ってます。

元々このMakerUNOはマレーシアで子供の教育用に考えて作られているので色々な機能が最初から入っています。

電源入れるとお馴染みのテーマが流れるでしょう。

Micro USBケーブルは付いてないので一緒に買って下さい。

$6の「Maker Uno」は STEM教育特化Arduino。マジックで名前を書ける。マレーシアのスタートアップ Cytronが教育者と連携して開発
スイッチサイエンスのパートナーでもある、マレーシア・ペナンのCytronが作った Maker Uno すごく面白い製品です。 学校教育に特化したArduino互換品。
Quora Userさんのプロフィール写真

NULLをマクロでなくキーワードとする(キーワードなら小文字が適切ですが)

整定数をポインタにできる、ということと、整定数0をヌルポと解釈されるの両立は無理すぎます。c++ができたとき、型チェックを厳しくしようとしたのにc言語のヌルポの仕様のお陰でヌルポだけはかえってややこしくなった。(C言語なら、まだNULLを ((void *)0)と展開すればヌルポであることがコンパイラに伝わったが、C++ではこれができなくなった)。

Quora Userさんのプロフィール写真

質問の意図は「何を理解すればC言語が簡単に思えるのか?」だと思いました。

C言語は難しい、はあまり正確な表現ではないでしょう。より正確には

  • C言語は簡単だけど、使い熟すのが難しい

と理解すれば、何を理解すれば簡単になるのか?も分かります。

現在ある数えきれないプログラミング言語に比べ、C言語の仕様は「簡単かつ単純」です。C言語の仕様は難しくないです。暗記が必要な言語仕様の数は高級言語の中では最小の部類だと思います。(今時Cは高級言語ではないとか、純粋関数型言語の方が単純だとか、は置いておきます)

C言語が難しいとされる最大の原因は以下の4つではないでしょうか?

  • プログラマがやりたい事を「実装する為の注意点が多い」こと(メモリ管理とポインタ管理)
  • その注意点は「他の言語では理解していなくても実装できてしまう」こと
  • 注意不足による問題の多くが「どこが問題の発生箇所か簡単には判らない」こと
  • 更に仕様が単純であるため、他の言語では「単なる機能である物もコーディングしなければならない」こと

まとめると

  • C言語は単純で覚えるのは容易な言語
  • ただし、使い熟すには技術が必要

であるため「C言語は難しい」とよく言われるのだと思います。

  • メモリ管理
  • ポインタ管理
  • デバッグ

この三つを使いこなす技術があれば、「いちいちコードを書くのが面倒臭い」とは思っても、他の言語に比べて難しいとは思わなくなると思います。

確実なメモリ管理/ポインタ管理

質問の意図は「何を理解すればC言語が簡単に思えるのか?」だと思いました。

C言語は難しい、はあまり正確な表現ではないでしょう。より正確には

  • C言語は簡単だけど、使い熟すのが難しい

と理解すれば、何を理解すれば簡単になるのか?も分かります。

現在ある数えきれないプログラミング言語に比べ、C言語の仕様は「簡単かつ単純」です。C言語の仕様は難しくないです。暗記が必要な言語仕様の数は高級言語の中では最小の部類だと思います。(今時Cは高級言語ではないとか、純粋関数型言語の方が単純だとか、は置いておきます)

C言語が難しいとされる最大の原因は以下の4つではないでしょうか?

  • プログラマがやりたい事を「実装する為の注意点が多い」こと(メモリ管理とポインタ管理)
  • その注意点は「他の言語では理解していなくても実装できてしまう」こと
  • 注意不足による問題の多くが「どこが問題の発生箇所か簡単には判らない」こと
  • 更に仕様が単純であるため、他の言語では「単なる機能である物もコーディングしなければならない」こと

まとめると

  • C言語は単純で覚えるのは容易な言語
  • ただし、使い熟すには技術が必要

であるため「C言語は難しい」とよく言われるのだと思います。

  • メモリ管理
  • ポインタ管理
  • デバッグ

この三つを使いこなす技術があれば、「いちいちコードを書くのが面倒臭い」とは思っても、他の言語に比べて難しいとは思わなくなると思います。

確実なメモリ管理/ポインタ管理を行うのが難しいので、何時まで経っても簡単だ、とは思えない可能性が高いですが。

今時の言語は「Write once, Run everywhere」という感じですが、C言語でクロスプラットフォームな実用プログラムを書こうとすると大変です。この観点からC言語の難しさを考えると

  • 仕様が定義されていなくて、処理系依存の物がある

があります。

「このコンパイラでは思った様に動作するにのに、こっちのコンパイラでは思った様に動作しない」「このCPUでは思った様に動作するのに、こっちのCPUでは思った様に動作しない」といったことが起こります。

  • ライブラリの差異

も難しく感じる原因になっていると思います。大量の#ifdefが必要になったります。難しいというより「面倒臭い」「手間がかかりすぎ」という問題ですが、実際にクロスプラットフォームなプログラムを書く場合の困難さはかなり大きいです。

これらのクロスプラットフォームで動作するプログラムの記述がかなり面倒だという現実も「C言語が難しい」と感じる原因の一つだと思います。

しかし、これらは「C言語が難しい」のではなく「クロスプラットフォームなプログラムを書くには手間がかかりすぎる」が問題で、C言語習得の難易度とは異なる問題だと思います。

三浦 英樹さんのプロフィール写真

C言語より前にも例えばPascalやLispはスタック領域・ヒープ領域を前提とした言語仕様ですので、これらは存在したことは間違いないです。

しかし、IBM360にはCPUの概念としてスタック領域・ヒープ領域という概念は有りません。もちろん、メモリは有るのでこれらをシミュレートすることは出来ますが。

スタックという概念が生まれたのが1955年とのことです。

Stack (abstract data type) - Wikipedia
Abstract data type Similarly to a stack of plates, adding or removing is only practical at the top. Simple representation of a stack runtime with push and pop operations. In computer science , a stack is an abstract data type that serves as a collection of elements with two main operations: Push , which adds an element to the collection, and Pop , which removes the most recently added element. Additionally, a peek operation can, without modifying the stack, return the value of the last element added. The name stack is an analogy to a set of physical items stacked one atop another, such as a stack of plates. The order in which an element added to or removed from a stack is described as last in, first out , referred to by the acronym LIFO . [ nb 1 ] As with a stack of physical objects, this structure makes it easy to take an item off the top of the stack, but accessing a datum deeper in the stack may require removing multiple other items first. [ 1 ] Considered a sequential collection, a stack has one end which is the only position at which the push and pop operations may occur, the top of the stack, and is fixed at the other end, the bottom . A stack may be implemented as, for example, a singly linked list with a pointer to the top element. A stack may be implemented to have a bounded capacity. If the stack is full and does not contain enough space to accept another element, the stack is in a state of stack overflow . Stacks entered the computer science literature in 1946, when Alan Turing used the terms "bury" and "unbury" as a means of calling and returning from subroutines. [ 2 ] [ 3 ] Subroutines and a two-level stack had already been implemented in Konrad Zuse 's Z4 in 1945. [ 4 ] [ 5 ] Klaus Samelson and Friedrich L. Bauer of Technical University Munich proposed the idea of a stack called Operationskeller ("operational cellar") in 1955 [ 6 ] [ 7 ] and filed a patent in 1957. [ 8 ] [ 9 ] [ 10 ] [ 11 ] In March 1988, by which time Samelson was deceased, Bauer received the IEEE Computer Pioneer Award for the invention of the stack principle. [ 12 ] [ 7 ] Similar concepts were independently developed by Charles Leonard Hamblin in the first half of 1954 [ 13 ] [ 7 ] and by Wilhelm Kämmerer [ de ] with his automatisches Gedächtnis ("automatic memory") in 1958. [ 14 ] [ 15 ] [ 7 ] Stacks are often described using the analogy of a spring-loaded stack of plates in a cafeteria. [ 16 ] [ 1 ] [ 17 ] Clean plates are placed on top of the stack, pushing down any plates already there. When the top plate is removed from the stack, the one below it is elevated to become the new top plate. Non-essential operations [ edit ] In many implementations, a stack has more operations than the essential "push" and "pop" operations. An example of a non-essential operation is "top of stack", or "peek", which observes the top element without removing it from the stack. [ 18 ] Since this can be broken down into a "pop" followed by a "push" to return the same data to the st

ヒープ領域というのがメモリのある領域を割り当てたり、解放することのできるメモリ管理(Region-based memory management)が出来る領域だとすると、発明されたのは1967年とのことです。

Region-based memory management - Wikipedia
Memory allocation scheme In computer science , region-based memory management is a type of memory management in which each allocated object is assigned to a region . A region, also called a partition , subpool , zone , arena , area , or memory context , is a collection of allocated objects that can be efficiently reallocated or deallocated all at once. Memory allocators using region-based managements are often called area allocators , and when they work by only "bumping" a single pointer, as bump allocators . Like stack allocation , regions facilitate allocation and deallocation of memory with low overhead; but they are more flexible, allowing objects to live longer than the stack frame in which they were allocated. In typical implementations, all objects in a region are allocated in a single contiguous range of memory addresses, similarly to how stack frames are typically allocated. In OS/360 and successors , the concept applies at two levels; each job runs within a contiguous partition [ a ] or region. [ b ] Storage allocation requests specify a subpool, and the application can free an entire subpool. Storage for a subpool is allocated from the region or partition in blocks that are a multiple of 2 Kib [ c ] or 4 KiB [ d ] that generally are not contiguous. As a simple example, consider the following C code which allocates and then deallocates a linked list data structure: Region * r = createRegion (); ListNode * head = NULL ; for ( int i = 1 ; i <= 1000 ; i ++ ) { ListNode * newNode = allocateFromRegion ( r , sizeof ( ListNode )); newNode -> next = head ; head = newNode ; } // ... // (use list here) // ... destroyRegion ( r ); Although it required many operations to construct the linked list, it can be quickly deallocated in a single operation by destroying the region in which the nodes were allocated. There is no need to traverse the list. Simple explicit regions are straightforward to implement; the following description is based on the work of Hanson. [ 1 ] Each region is implemented as a linked list of large blocks of memory; each block should be large enough to serve many allocations. The current block maintains a pointer to the next free position in the block, and if the block is filled, a new one is allocated and added to the list. When the region is deallocated, the next-free-position pointer is reset to the beginning of the first block, and the list of blocks can be reused for the next allocated region. Alternatively, when a region is deallocated, its list of blocks can be appended to a global freelist from which other regions may later allocate new blocks. With either case of this simple scheme, it is not possible to deallocate individual objects in regions. The overall cost per allocated byte of this scheme is very low; almost all allocations involve only a comparison and an update to the next-free-position pointer. Deallocating a region is a constant-time operation, and is done rarely. Unlike in typical garbage collection systems,
Quora Userさんのプロフィール写真

Cは今でも非常に明確で安定した役割を持つ、現代でもよく使われている言語の一つです。45年という人気のコンピュータ言語の中でも最長老級の言語でありながら、Cは今でも第9位と意外に高いシェアを保っています。(GitHubより)

面白いことに、Cの最大の武器は「低機能であること」です。どんどん機能を追加して便利になる数多くのコンピュータ言語に囲まれながら、唯一独自の路線をいく言語と言えます。

そんなCの現代の用途として真っ先に挙げるべきなのは、「コンピュータ言語の開発」です。

実は、トップクラスの知名度を誇るPython、Ruby、PHPの実行環境は全てCで開発されています。つまり様々なスクリプト言語の活躍は、この分野で圧倒的なシェアを持つCが今も背後で支えて成り立っているのです。(JavaScriptのV8はC++)

C「大地と共に生き、自らの足で大地を駆ける、それが私達だ」(画像はBing Image Creatorで生成)

自動化機能をほとんど持たないCは、メモリやハードウェアの管理をほぼ全て手作業で行う必要があります。その代わり、プログラマーの頭の中に思い浮かんだどんなに複雑で難解なデータ構造やアルゴリズムも、記述通りメモリ上に理想的な配置で表現し、挙動を完全に制御できます。さらに速度が必要な場合はコード内にアセンブリ言語を直接記述できます。何もかも手作業な上にミスが許されないため、Cは利

Cは今でも非常に明確で安定した役割を持つ、現代でもよく使われている言語の一つです。45年という人気のコンピュータ言語の中でも最長老級の言語でありながら、Cは今でも第9位と意外に高いシェアを保っています。(GitHubより)

面白いことに、Cの最大の武器は「低機能であること」です。どんどん機能を追加して便利になる数多くのコンピュータ言語に囲まれながら、唯一独自の路線をいく言語と言えます。

そんなCの現代の用途として真っ先に挙げるべきなのは、「コンピュータ言語の開発」です。

実は、トップクラスの知名度を誇るPython、Ruby、PHPの実行環境は全てCで開発されています。つまり様々なスクリプト言語の活躍は、この分野で圧倒的なシェアを持つCが今も背後で支えて成り立っているのです。(JavaScriptのV8はC++)

C「大地と共に生き、自らの足で大地を駆ける、それが私達だ」(画像はBing Image Creatorで生成)

自動化機能をほとんど持たないCは、メモリやハードウェアの管理をほぼ全て手作業で行う必要があります。その代わり、プログラマーの頭の中に思い浮かんだどんなに複雑で難解なデータ構造やアルゴリズムも、記述通りメモリ上に理想的な配置で表現し、挙動を完全に制御できます。さらに速度が必要な場合はコード内にアセンブリ言語を直接記述できます。何もかも手作業な上にミスが許されないため、Cは利用者の高い技量が求められる極めて難易度の高い言語ですが、ハードウェアを完璧に乗りこなし、究極の演算効率を目指す熟練プログラマーにとっては唯一無二の言語なのです。

Cのもう一つの大切な用途は、「小規模組み込み系開発」です。

Cは長年、リモコンや炊飯器などOSを介さずに動作する小規模なハードウェア制御プログラムの開発でよく使用されています。これら電化製品に組み込まれた低性能なチップ上で動作できるのはアセンブリかCだけです。リモコンや炊飯器など、家にある小型の電化製品のほとんどはCのプログラムで制御されていると思って良いでしょう。

特に、乾電池1、2本で数年間動作する超省電力なリモコンのように、身の回りに当たり前にあるにも関わらず、今の所Cかアセンブリ以外では書きたくても書けないプログラムも実はたくさんあります。逆に組み込み系でもそれなりな性能のチップで省電力にあまり気を使わなくて良いものや、ある程度以上大規模なものはC++で開発されていることが多いです。

ちなみに、よく「C/C++」と書かれて混同されがちなCとC++ですが、これら2つの言語は全くの別物です。

C++は次々と先進的な機能を取り入れて成長する「最新の言語」の1つですが、Cは「機能的に進化しない言語」です。Cはめったに(10年以上)バージョンアップが起きない上に、マイナーレベルのアップデートしかしません。この2つの言語はそれぞれ全く異なった目的と意義を持った言語ですので、ちゃんと別々に扱ってあげて下さい。

Cは時代に流されずに独自の文化を守る、誇り高い孤高の言語です。また私たちのすぐそばで働く身近な言語でもあります。忘れ去られた言語などでは全くありませんので悪しからず。

Kengo Nakajimaさんのプロフィール写真

プログラミング言語は、大まかにインタプリタ言語とコンパイル言語に分けることができます。

インタプリタ言語はRuby,Python,PHP,JavaScriptのような言語で、コンパイル言語はJava,C,C++,C#,Go,Rustのような言語です。

どのプログラミング言語も、最終的には0と1のビットの羅列しか処理できないCPUを駆動しているのですから、人間が入力した文字を何らかの方法でビット列に変換しています。このビット列をマシン語といいます。マシン語はCPUのメーカーごとに規格が違うので、IntelのCPU用のマシン語ファイルをARMのCPUで動かすことはできません。

プログラムを動作させているときにマシン語に変換するのがインタプリタ言語で、プログラムを動作する前にマシン語に変換して、ファイルに保存しておき、実行時にマシン語への変換をしないのがコンパイル言語といいます。

一般にコンパイル言語のほうがだいぶ高速ですが、インタプリタ言語ならばCPUの種類によらず動きます。

コンパイル言語は、CPUの種類に依存しないバイトコードにいったん変換しておく言語とそうでない言語にわかれます。JavaやC#などはバイトコードを使う言語で、C/C++/RustやGolangのように直接マシン語に変換します。バイトコードを使う言語は、実行時にマシン語に変換します。バイトコードを使う言語は、基本的には、インタ

プログラミング言語は、大まかにインタプリタ言語とコンパイル言語に分けることができます。

インタプリタ言語はRuby,Python,PHP,JavaScriptのような言語で、コンパイル言語はJava,C,C++,C#,Go,Rustのような言語です。

どのプログラミング言語も、最終的には0と1のビットの羅列しか処理できないCPUを駆動しているのですから、人間が入力した文字を何らかの方法でビット列に変換しています。このビット列をマシン語といいます。マシン語はCPUのメーカーごとに規格が違うので、IntelのCPU用のマシン語ファイルをARMのCPUで動かすことはできません。

プログラムを動作させているときにマシン語に変換するのがインタプリタ言語で、プログラムを動作する前にマシン語に変換して、ファイルに保存しておき、実行時にマシン語への変換をしないのがコンパイル言語といいます。

一般にコンパイル言語のほうがだいぶ高速ですが、インタプリタ言語ならばCPUの種類によらず動きます。

コンパイル言語は、CPUの種類に依存しないバイトコードにいったん変換しておく言語とそうでない言語にわかれます。JavaやC#などはバイトコードを使う言語で、C/C++/RustやGolangのように直接マシン語に変換します。バイトコードを使う言語は、実行時にマシン語に変換します。バイトコードを使う言語は、基本的には、インタプリタ言語より速いけど、マシン語に直接変換する言語より速いです。バイトコードを使えば、インタプリタ言語とコンパイル言語の間のようなものを作れるということです。

実はここまで、大事なことを書かずにいました。上記に挙げたような著名な言語については、ひとつの言語について、処理系がたくさんあります。たとえばRubyであれば、MRI, JRuby, IronRuby, mrubyなど10以上、小さいのもふくめたらもっとあります。それらには、インタプリタ処理系もバイトコード処理系もコンパイルをする処理系もあります。 ( Rubyアソシエーション: Ruby処理系の概要 ) MRIは以前はインタプリタ言語でしたが、いまはバイトコード言語になっています。重要なのは、言語仕様と処理系の仕様は独立しているということです。

Rubyの処理系で私が期待しているのは、Rubyの言語仕様を一部削減したサブセット言語を定義し、それを直接マシン語に変換するmmc という処理系です。 ( Ruby 3 の型解析に向けた計画 ) このようにインタプリタ言語やバイトコード言語の言語仕様のサブセットを定義してマシン語へのコンパイルをする処理系のひとつには、UnityのC# Burstコンパイラがあります。 WebAssembly規格もそうしたもののひとつかもしれません。

上記をまとめると、現在は、言語仕様と処理系の仕様は独立しているので、ある言語を何のために使いたいかによって、必要な機能をもつ処理系を使い分けると良い、ということです。

羽田 美廣さんのプロフィール写真

概念的なものでしたら、仮想メモリーや、動的メモリー割合てに関連する技術とその仕組みのことですので、20世紀後半ぐらいに、ちょうどコンピューター黎明期にあたる時期に様々なアイデアが生まれ、実装されています。

その後、「ヒープ」と「スタック」のような固有名称として認識が広まりはじめるのは、C言語やJavaが増えつつ時代ですので、だいたい20世紀末期ぐらいだろうと思いますで。

もともと、メモリーへのスタックの考え方などは、C言語が無かった時代でも、そのままアセンブリ言語では、レジスタの値を退避するため、PUSH POP命令を使ってスタック動作を実現しています。

たたし、これだと直前に対比したレジスタの値を戻のには便利ですが、複数のレジスタが必要なサブルーチンでは何かと都合が悪いのです。

そこで、スタックの開始位置をCPU任せにしない方法として、色々な実装方法が利用されていました。その色々な方法を「スタック」ではない別の方法のひとつに、ヒープと同様の仕組みもありました。

Quora Userさんのプロフィール写真

あの時代が必要とした要件(高速性、高効率な実行を実現するために最適化コンパイラをさほど必要としない低抽象度の実行モデル)を、適切なタイミングで提供したから、でしょうか。

高級過ぎて低レベル操作がしにくかったり、最適化が必要でコンパイルに時間がかかる言語などは、時代の要請にはマッチしませんでした。

さらに、「その時代の優秀な人材を引き付ける」というのは、成功する言語には必須の条件に思います。当時、UNIXが隆盛をむかえようとしていて、OSやネットワーキングの技術で世界をかえていこうという優秀な人材が、UNIXに近づき、その実装言語であったC言語のアクティブユーザーになっていった、みたいな動向もCの発展・利用者拡大に寄与した要素であろうと思います。言語が優秀なだけではなく、利用者が優秀であることが言語の成功にとって重要です。

Daisuke Sawadaさんのプロフィール写真

プログラミング言語界ということで今でもC言語が得意な低レイヤは除いてプログラミング言語だけで考えても、まだまだ影響力があるという意味では偉いです。

Rubyの中を覗くとC言語で書いてる(例:object.c

PHPの中を覗くとC言語で書いてる(例:zend_objects.c

Pythonの中を覗くとC言語で書いてる(例:object.c

C言語でコード書く機会はまったくないとしても、C言語で書かれたコードの恩恵をまったく受けていないと言い切れるプログラマは少ないはず。

コンパイラ基盤であるLLVMやJavaScriptのエンジンV8なんかはC++が多めです。CだけじゃなくC++も含めて考えたらさらに偉すぎです。

JavaとかC#とかは自身の言語で書かれている割合が高め。そしてRustは(LLVM除けば)Rust自身、つまりセルフホスティング言語。Rustしか書いてないなら「C言語なにそれ」と言い張っても誰も突っ込めないでしょう。

Quora Userさんのプロフィール写真

日本の場合、一時期のCマガジンの存在が大きいかなって思ったりします。あれで育った現役プログラマーって結構多いと思いますよ。

フロッピーの時代から毎号付いてくるフリー版LSI C86に、年度が変わると第1回から始まるC言語入門、ちょっと背伸びすれば入門者でも手に届く中級者向けチョイスの特集に、上級者向けのマニアックな連載と、最高のバランスで構成された良い雑誌だったと思います。

プロ向けCが滅茶苦茶高かった時代に、MS-DOSが走るパソコンさえ持っていれば、誰でもすぐに勉強を始められた手軽さがよかったです。故に、最初の言語がCでも問題無かったのです。

Naoki Fujitaさんのプロフィール写真

int* a;とint *a;がどちらも正しいこと。意味論的にはint* aと解釈したいが、int* a,b;はバグを呼ぶコードで、int *a,*b;としないと構文上正しくない。

さらに*が参照解決にも使われるシンボルなので、ややこしい。同じシンボルに2つの意味(ポインタの宣言と参照解決)をつけたことで学習者の不安はマッハに。

素直にポインタや参照っていうのはタダのメモリアドレス(番地)のことだと言えばいいのに、参照やポインタなる深遠なる響きが実態と乖離した哲学へといちいち学習者を誘おうとする。(規格上はそう表現されているしC++を含めると参照とポインタは違うのでさらにややこしい。)

正直なところシンタックスが微妙なことが学習者の理解を大きく損ね続けている原因の一つだと考えられる。

1.ポインタと参照はメモリアドレスと読み替えてよい。

2.ポインタ変数を宣言する*と参照を解決する*は同じシンボルを使っているが違う意味だ。私は頭の中で後者をderef(参照剥がし)と呼んでいる。

3.意味的にはint*(int型への8byteまたは4byteのアドレス値)とみてよい、しかしint* a,b; int* aとint bの宣言になる。これはCの構文定義がおかしいと思う。(が規格化されているので抵抗する意味はない。) 自分で書くときはint* a; int* b;と分けて宣言することで正気を保つ。

4.

int* a;とint *a;がどちらも正しいこと。意味論的にはint* aと解釈したいが、int* a,b;はバグを呼ぶコードで、int *a,*b;としないと構文上正しくない。

さらに*が参照解決にも使われるシンボルなので、ややこしい。同じシンボルに2つの意味(ポインタの宣言と参照解決)をつけたことで学習者の不安はマッハに。

素直にポインタや参照っていうのはタダのメモリアドレス(番地)のことだと言えばいいのに、参照やポインタなる深遠なる響きが実態と乖離した哲学へといちいち学習者を誘おうとする。(規格上はそう表現されているしC++を含めると参照とポインタは違うのでさらにややこしい。)

正直なところシンタックスが微妙なことが学習者の理解を大きく損ね続けている原因の一つだと考えられる。

1.ポインタと参照はメモリアドレスと読み替えてよい。

2.ポインタ変数を宣言する*と参照を解決する*は同じシンボルを使っているが違う意味だ。私は頭の中で後者をderef(参照剥がし)と呼んでいる。

3.意味的にはint*(int型への8byteまたは4byteのアドレス値)とみてよい、しかしint* a,b; int* aとint bの宣言になる。これはCの構文定義がおかしいと思う。(が規格化されているので抵抗する意味はない。) 自分で書くときはint* a; int* b;と分けて宣言することで正気を保つ。

4.関数ポインタやポインタのポインタが出てきたら諦める。

長谷川 祐一さんのプロフィール写真

『今でもC言語が使われている』???

『そんなわけがない!』

そんなものは、採用するICの大きさ制御の細かさで決めるのです。

デカイICでLinuxが載っててユーザーインターフェースなんていうトロトロタイミングなのにCを使う、なんて案件は単にロートルが牛耳っているだけです。

Linuxが乗るんならシェルスクリプトでもパイソンでもできるんじゃないか?(パイソン私使えないけどシェルスクリプト+awkっていう試作ならやった)

今の時代になっても、赤外線リモコンにLinuxは流石に載せませんよね、いやµITRONも載せませんよね、RAM数kB(数百Bかも)、フラッシュ数kB、クロック数百kHz、I/OはGPIOだけ(調歩同期シリアルも無いかも) 位の。あのPC-8001より小さな構成のマイコンに書くのなら、

Cでもきついかもしれない、スタック領域の確保で苦労するのでメモリマップと変数マップを丁寧に紙か表計算で書いて、アセンブラ直書きしないとできないかもしれません。

これがH8/300あたり、RAM16k~32kとかもっとになるとCがだいたい楽勝になって、

H8/500とかになるとOS載せたいね、ってITRON仕様のOSを買うか書くか、なんて昔はなっていて。

SHや68020とかになるとLinux載せられん?って言い出すわけですよ。

Quora Userさんのプロフィール写真

C#は知りません。c++について言えば、言語が巨大なので生きているうちに言語仕様の大まかな部分でさえ学べないだろうというところとか、オブジェクト指向などマルチパラダイム言語であることです。デストラクタをvirtualにすべきか否か?で迷いますし。

Effective c++ ぐらいをある程度覚えてプログラムを作れるようになれば私としては上出来ですが、テンプレートプログラミングなどをやろうとしたら、せっかく覚えたところを一旦忘れなければいけないらしいです。boostのような暗黒大陸に乗り込むのは怖すぎます。

c++ではc程マクロを使わずにすむのも大きいですね。cのマクロは柔軟ですが柔軟すぎていくらでも危険なプログラムをかけますし、デバッグも難しい。例外処理ができることも。

細かい違いとしては、'a' はCではint型ですがC++ではchar型です。またconstの扱いも違います。ソースファイルのトップレベルで const int nnn = 〜; とやった場合、cでは外部リンケージですがC++では内部リンケージです。その他細かな違いがあるので、C++はCの上位言語だと思いこんでいると落とし穴に引っかかると思います。

実は私の知ってるCは大昔のものなので間違いがあるかもしれませんが、c99とて互換性を保つため大昔のcの仕様を引き継いでいる筈だ、と考えて回答いたしました。

Quora Userさんのプロフィール写真

アラインされていないデータはCPUが自然な形でアクセスすることができなくなり、十分な速度が出せなくなるからです。

たとえば32bit CPUは32bit単位でRAMやキャッシュとデータのやりとりをしています。32bitにアラインされたデータであれば(アドレスの下位2ビットが0であれば)、1度の読み出しで読み出すことができるのですが、中途半端な位置に置かれていると、2回読み出したものを合体させて32bitのデータにしなければいけないので遅くなってしまいます。書き込みも同様です。

そもそも32ビットにアラインされていない32ビットのデータを読み出すことができないようなCPUも存在します。そのようなCPUでは、ユーザのプログラムでアライメントが理由でデータが読み出せなかったときにアライメント例外が発生します。その例外をOSがハンドルして、ユーザのプログラムがあたかもデータを読み出したかのように処理を行なうことも可能ですし、そうじゃなくプログラムを異常終了させてしまうことも可能です。前者の場合プログラムは動きますが動作が極めて遅くなります。

Quora Userさんのプロフィール写真

初心者ですが(何年初心者やってるのだろう?)

クラスの宣言ではpublicのものだけ宣言したくともprivateな部分の宣言も必要だったり。

shared_ptr を引数にするときは、const参照渡しがよいと聞いたが、それだと、結局shared_ptrの本体はどこにあるのか(ダングリングしてはまずい)に頭使ってしまったり。

デストラクタをvirtualにするべきかしないべきか判断が難しかったり。std :: input_iterator_tag は継承するクラスだけどvirtualなデストラクタがあったらまずいよね。STLコンテナをpublic継承したら危険だったり。たとえば、std::vector のイテレータはコピーコンストラクタが公開されているので、newで作ることができてしまったり。

最近は流行らないようですが、ポリモルフィックな使い方を予定しているクラスFooで必ずshares_ptr<Foo> かshared_ptr<Fooの子クラス>の形で使うことにするために、Fooのコンストラクタをprotectedにしたら、make_shared が使えなくなったり。

int main(int argc, char const * const * const argv)… というシグニチャを許容する処理系は沢山あるが、argvが書き換えられない保障はなかったり。

  1. class Foo { 
  2. pr 

初心者ですが(何年初心者やってるのだろう?)

クラスの宣言ではpublicのものだけ宣言したくともprivateな部分の宣言も必要だったり。

shared_ptr を引数にするときは、const参照渡しがよいと聞いたが、それだと、結局shared_ptrの本体はどこにあるのか(ダングリングしてはまずい)に頭使ってしまったり。

デストラクタをvirtualにするべきかしないべきか判断が難しかったり。std :: input_iterator_tag は継承するクラスだけどvirtualなデストラクタがあったらまずいよね。STLコンテナをpublic継承したら危険だったり。たとえば、std::vector のイテレータはコピーコンストラクタが公開されているので、newで作ることができてしまったり。

最近は流行らないようですが、ポリモルフィックな使い方を予定しているクラスFooで必ずshares_ptr<Foo> かshared_ptr<Fooの子クラス>の形で使うことにするために、Fooのコンストラクタをprotectedにしたら、make_shared が使えなくなったり。

int main(int argc, char const * const * const argv)… というシグニチャを許容する処理系は沢山あるが、argvが書き換えられない保障はなかったり。

  1. class Foo { 
  2. private: 
  3. Foo* const mythis = this; 
  4. .... 
  5. }; 

とかやると、mutable がなくとも、constメンバ関数で状態を書き換えられたり。

  1. #define private public 

とかやると大変なことになったり。

その他そのた、うっかりミスをしやすいところ。

Quora Userさんのプロフィール写真

異常値を伝えるためにNULLを使うのはやめる。

Oami Kiyokazuさんのプロフィール写真

基本データ型(char, int, short int, long int, long long int)のサイズを処理系依存にしない。

charは8bit固定

intにshortやlongの修飾語を付けるのも煩わしいので、int16やint32などビット数を明示する形が良いですね。

Yojiさんのプロフィール写真

マクロは無くす。

FURUKAWA Hiroshiさんのプロフィール写真

ベル研に火をつける。

Daisuke Sawadaさんのプロフィール写真

intのバイトサイズだけは固定で。

Naoya Yamaguchiさんのプロフィール写真

スコープを抜ける際に、戻り値を除き、スコープ内で確保されたリソースを全て解放するようにする。