CとCPP - kentakozuka/yetos GitHub Wiki
一次式
-
識別子
-
定数
-
文字列リテラル
-
式を
()で囲んだもの -
式に対して演算子を適用したり、演算子で式と式をつなぎ合わせたものも式
-
すべての式は型を持つ
-
式がどこかの記憶領域を意味している場合は左辺式(lvalue, locator value)
-
式が単なる値を意味している場合は右辺式
- 識別子(変数名または関数名)からスタート
- 識別子に近いほうから優先順位に従って派生型を解釈する。優先順位は次の順;
- 宣言をまとめるための
() - 配列の
[]、関数の() - ポインタの
*
- 型指定子(左端にある
int,doubleなど)を解釈する
型名は識別子の宣言から識別子を取り除く
| Declaration of identifier | English | Declaration of type | English |
|---|---|---|---|
int foo; |
foo is int. | int |
type of int |
int foo[3]; |
foo is array[3] of int. | ||
int foo[3][4]; |
foo is array[10] of array[4] of int. | ||
int *foo[3]; |
foo is array[3] of pointer to int. | ||
int (*foo)[3]; |
foo is pointer to array[3] of int. | int (*)[3]; |
type of pointer to array[3] of int. |
int foo(int a); |
foo is function(int a) returning int. | ||
int (*foo)(int a); |
foo is pointer to function(int a) returning int. | int (*)(int a); |
type of pointer to function(int a) returning int. |
新・標準プログラマーズライブラリ C言語 ポインタ完全制
int (*foo[10])(int a);は「foo is array[10] of pointer to function(int a) returning int」において、
- "int" は 基本型(basic type)
- それ以外は派生型(derived type)
分類は3つ
- オブジェクト型(char, int、配列、ポインタ、構造体など)
- 関数型
- 不完全型
3種類
- ポインタ
- 配列
- 関数
- サイズは派生元の型のサイズ
- Cでは多次元配列は配列の配列
- サイズは派生元の型のサイズ
- 関数型からはポインタ型以外は派生できない
- 配列型から関数型を派生できない
| 型名 | 概要 |
|---|---|
| 基本型 | 処理系依存 |
| ポインタ | 処理系依存 |
| 配列 | 派生元の型のサイズ * 配列の要素数 |
| 関数 | サイズは計算できない |
-
operator
-
式に対して何らかの演算を行い、その結果をの式を返す記号
-
&: アドレス演算子 -
*: 間接演算子 -
[]: 添字演算子
int *foo_p;このときの * は区切り子であり、間接演算子ではない。
- それが指し示す変数の全体の場所を示す
- 普通の変数と同様、メモリ上に配置
- 「ポインタ型の値」とはメモリアドレス
- ポインタがどんな型を指しているかはコンパイラが覚えている
- メモリサイズはCPUによって決まる。最近のCPUでは4バイトが多い。
- 実行時、ポインタ変数の実体はアドレスの値が入るだけ。
- コンパイル時は以下の情報も含む
- 領域の大きさ
- それが指し示すものの型
| コンパイル時 | 実行時 |
|---|---|
| アドレス, 領域のサイズ, 実体の型 | アドレス |
- コンパイラはポインタがどんな型を指しているかを覚えている
- 1を加算するとそのポインタが指す型のサイズだけ増加される
- 式の中では配列は「その先頭要素へのポインタ」に読み替えられる
-
p[i]は*(p + i)のシンタックスシュガー
ai = arr[i];は
-
arrが先頭アドレスに指し -
arr + iでポインタ演算が行われ -
*(arr + i)で配列のi番目の要素の値がaiに入る
ポインタを使う方が配列をイテレートする速度が早い理由
現在ではコンパイラが最適化を行うため、差がでることはまずない
for (i = 0; i < max; i++) {
// do something with arr[i]
}ループ内にポインタ演算 (*(arr + i))が発生する
for (p = &arr[0]; p != &arr[max]; p++) {
// do something with arr[p]
}ループ内にポインタ演算は発生しない発生する
void foo() {
int i = 42;
int* p = &i;
int r1 = *p;
*p = 1;
int r2 = i;
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
int* q = reinterpret_cast<int*>(addr);
}_Z3foov:
push rbp
mov rbp, rsp
; int i = 42;
mov dword ptr [rbp - 4], 42
; int* p = &i;
lea rax, [rbp -4]
mov qword ptr [rbp - 16], rax
; int r1 = *p;
mov rax, qword ptr [rbp - 16]
mov ecx, dword ptr [rax]
mov dword ptr [rbp - 20], ecx
; *p = 1;
mov rax, qword ptr [rbp - 16]
mov dword ptr [rax], 1
; int r2 = i;
mov ecx, dword ptr [rbp -4]
mov dword ptr [rbp - 24], ecx
; uintptr_t addr = reinterpret_cast<uintptr_t>(p);
mov rax, qword ptr [rbp - 16]
mov qword ptr [rpb - 32], rax
; int* q = reinterpret_cast<int*>(addr);
mov rax, qword ptr [rbp - 32]
mov qword ptr [rbp - 40], rax
pop rbp
ret_Z3foov:
push rbp
mov rbp, rsp
; int i = 42;
mov dword ptr [rbp - 4], 42- メモリアドレス
[rbp - 4](変数i) に42を書き込む -
[xxx]をアドレスxxxへのメモリアクセス(インテル記法) -
dwordはメモリアドレスが4バイトを示す - ptrは
[]がメモリアクセスだと明示する
; int* p = &i;
lea rax, [rbp -4]
mov qword ptr [rbp - 16], raxrax = rbp -4- メモリアドレス
[rbp - 16](変数p) にraxを書き込む -
qwordはメモリアドレスが8バイトを示す
; int r1 = *p;
mov rax, qword ptr [rbp - 16]
mov ecx, dword ptr [rax]
mov dword ptr [rbp - 20], ecx- p(
[rbp - 16])が指す先の値を読み、raxに書き込む -
raxが指すメモリ領域から4バイトを読み込み、ecxに書き込む -
ecxからr1([rbp - 20])に書き込む
; *p = 1;
mov rax, qword ptr [rbp - 16]
mov dword ptr [rax], 1 ; int r2 = i;
mov ecx, dword ptr [rbp -4]
mov dword ptr [rbp - 24], ecx ; uintptr_t addr = reinterpret_cast<uintptr_t>(p);
mov rax, qword ptr [rbp - 16]
mov qword ptr [rpb - 32], rax ; int* q = reinterpret_cast<int*>(addr);
mov rax, qword ptr [rbp - 32]
mov qword ptr [rbp - 40], rax pop rbp
ret| 命令 | 説明 | 備考 |
|---|---|---|
mov a, b |
b から a に値をコピー |
|
lea a, [b - c] |
メモリアドレスを計算した結果をレジスタに書き込む。a = b - c. |
Load Effective Address |
- ポインタ変数と普通の変数に違いはなく、どちらも
mov命令で読み書きするだけ -
[]を使ったメモリアクセスを2回やるか、1回やるかの違い
- new 演算子は動的にメモリを割り当てる
- OSのメモリ管理がないとできない
- libc++のnewは内部でmalloc()を呼んでいる
- 配置new (placement new, プレイスメントnew)は、インスタンスを特定のメモリアドレスに配置するための機能
- OSがなくても使える
- OSが入っていない組み込み機器でも簡単に使用できるツールが集まった標準ライブラリ。
- OSの機能に依存する部分(
mallocやprintfなど)は別の関数に切り出されており、使用者が独自に実装することができる。
- 引数の型や個数が異なる同名の関数を定義できるように、元々の関数名に引数の型名(+α)をくっつけたシンボルに変換される
- 関数マングリングを防ぐには
extern "C"を使う
- C++としてコンパイルするときに定義され、Cのコンパイルでは定義されない
#ifdef __cplusplus
// do something for only C++
#else
// do something else for C
#endif- 列挙体の型名の前の
enum - 構造体の型名の前の
struct
両方で使用する場合は、省略しない
構造体の各フィールドを詰めて配置するためのコンパイラ拡張。コンパイラは何も指定されていない場合、変数のアラインメントを守るためにフィールド間に隙間(パディング)を挿入する。しかし、ハードウェアの仕様で定まったデータ構造を構造体として表現するためには、コンパイラが勝手に隙間を挿入してはまずい。そのため、このコンパイラ拡張を指定して、隙間の挿入を防止する
UNIXというOSを開発するために作られた。(実際はCよりUNIXの方が先に作られた。)
- 1969年: UNIXの開発が開始。初期のUNIXはアセンブリで書かれた。(by Ken Thompson, Dennis Ritchie, and others)
- 1969年ごろ: Ken ThompsonがB言語を開発
- 1971年: Dennis RitchieがBを改良してNB(New B)を開発。後のC言語。
- 1973年: Ken ThompsonがUNIXをほぼすべてをCで書き直す
- 1978年: K&R本の初版発行。
- 1988年: ANSI C準拠の K&R本発行
- 1989年: ANSI C が採択
- 1990年: ANSI C が ISO の規格に採択(ISO-IEC 9899:1990)
- 1993年: ANSI C が JIS の規格に採択(JIS X3010:1993)