ここでは、ISO/IEC 9899:2017（通称 C17）の ''6.7 Declarations'' で定義されている構文と意味を読み解き、データ型と変数への理解を深めようと思います。
N2176 C17 ballot ISO/IEC 9899:2017 §6.7 ''Declarations''：宣言.
形式
''
''
''
''
記憶域クラス指定子( ''storage-class specifiers'' )は6種類あり、いずれもキーワードです。
ISO/IEC 9899:2017（通称 C17）の §6.11.5 ''Storage-class specifiers''（記憶域クラス指定子）を抄訳/引用します。
構文
''
制約事項
2 1つの宣言の中では、最大で1つの記憶域クラス指定子を与えることができます。ただし、_Thread_local は static または extern と共に使用することができます。
3 ブロックスコープを持つオブジェクトの宣言では、宣言指定子に _Thread_local が含まれる場合、 static または extern も含まれなければなりません。オブジェクトのいずれかの宣言に _Thread_local が含まれている場合、そのオブジェクトのすべての宣言に含まれていなければなりません。
4 _Thread_local は関数宣言の宣言指定子には含まれません。
セマンティクス
5 typedef 指定子は、構文上の利便性のためだけに ''記憶域クラス指定子'' と呼ばれていますが、これについては 6.7.8 typedef で説明します。様々な結合と保存期間の意味については6.2.2 ''Linkages of identifiers''（識別子の結合） と6.2.4 ''Storage durations of objects''（オブジェクトの記憶域期間） で説明ました。
6 register 記憶域クラス指定子を持つオブジェクトの識別子の宣言は、そのオブジェクトへのアクセスが可能な限り高速であることを示唆するものである。このような提案がどの程度有効であるかは、実装で定義される。
8 集成体( aggregate )や union オブジェクトがtypedef以外の記憶域クラス指定子をつけて宣言された場合、リンクに関するものを除き、記憶域クラス指定子の結果として得られる特性は、オブジェクトのメンバーにも適用され、さらに再帰的に集約オブジェクトやユニオンメンバーオブジェクトにも適用される。
前方参照：型定義（''type definitions''; 6.7.8）。
Constraints
Semantics
Forward references: type definitions (6.7.8).
typedefとは、データ型に型定義名をつけるためのキーワードである。
形式: 
typedef データ型 型定義名;
//例 typedefの使用例
typedef unsigned int UINT; // unsigned int型にUINTという別名をつける。
int main(void) {
  UINT value; 
}
typedefを使うことで、マルチプラットホームにおいて可搬性を高めたり
（データ型を変更したい場合、typedefの部分を変更するだけで、すべてのデータ型を変更できる。）、
データ型を隠蔽し、カプセル化を向上できる。
externを使うことで、複数のソースファイルで、1つの識別子を共有することができる。
externの使用例
extern int i;               //外部結合をもつ。
extern void function(void); //外部結合をもつ。このexternは省略可能。
int main(void) {
  i = 1;
  printf("最初のiの値は%d。\n", i);
  function();
  printf("function関数を呼び出した後のiの値は%d。\n", i);
}
int i;
void function(void) { i = 2; }
実行結果:
% cc file1.c file2.c -o prog
% ./prog
最初のiの値は1。
function関数を呼び出した後のiの値は2。
上の例では、myheader.h で外部参照宣言を一括して行い、定義元の file2.c でも myheader.h をインクルードしています。
この事により、宣言と定義で方が食い違っていないことを言語処理系が検査する機会を与えています。
extern を、.h でなく .c で使う時は、宣言と定義の食い違いのリスクを負っていることを意識すべきです。
----
異なるスコープで宣言された識別子や、同じスコープで2回以上宣言された識別子は、結合(''Linkages''演算子の結合性は ''associativity of operators''。JISは、2つの術語に1つの訳を与えてしまっている)と呼ばれるプロセスによって、同じオブジェクトや関数を参照するようにすることができます。
結合には、外部結合( ''external linkage'' )、内部結合( ''internal linkage'' )、および無結合( ''none linkage'' )の3種類があります。
プログラム全体を構成する翻訳単位やライブラリの集合の中では、外部結合された特定の識別子の宣言は、それぞれ同じオブジェクトや関数を意味します。
1つの翻訳単位( ''translation-unit'' )の中で、内部結合を持つ識別子の各宣言は、同じオブジェクトまたは関数を表します。無結合な識別子の各宣言は一意の実体を表します。
オブジェクトまたは関数のファイルスコープ識別子の宣言に、記憶域クラス指定子 static が含まれている場合、その識別子は内部リンクを持ちます。
識別子の事前宣言が可視化されているスコープにおいて、記憶域クラス指定子 extern で宣言された識別子について、事前宣言が内部または外部結合を指定している場合、後の宣言における識別子の結合は、事前宣言で指定された結合と同じである。先行宣言が見えない場合、または先行宣言が結合を指定していない場合、その識別子は外部結合を持つ。
関数の識別子の宣言に記憶域クラス指定子がない場合、その結合は、記憶域クラス指定子 extern で宣言された場合と全く同じように決定されます。
オブジェクトの識別子の宣言にファイルスコープがあり、記憶域クラス指定子がない場合、そのリンク先は外部となります。
オブジェクトまたは関数以外と宣言された識別子、関数のパラメータと宣言された識別子、記憶域クラス指定子 extern を持たずに宣言されたオブジェクトのブロックスコープ識別子は、結合されません。
1つの翻訳単位の中で、同じ識別子が内部リンクと外部リンクの両方で現れた場合、その動作は未定義です。
----
関数宣言に記憶域クラス指定子 static を含めることができるのは、ファイルスコープにある場合のみです6.7.10 storage-class specifiers。
ファイル有効範囲( file scope )をもつオブジェクト（変数や配列など）や関数を記憶域クラス指定子 static を伴って宣言することで、そのオブジェクトや関数を内部結合とし他の翻訳単位から参照できなくすることができます。
また、ブロック有効範囲のオブジェクトを記憶域クラス指定子 static を伴って宣言することで静的記憶域期間（''static storage duration''）をもたせることが出来、その生存期間はプログラム実行の全体になります。その値はプログラム開始処理の前に1回だけ初期化します。
//例 staticの使用例1
//※このプログラムはコンパイルエラーとなる。
// file1.c
extern int i;
extern void function();
int main(void) {
  i = 1;
  printf("最初のiの値は%d。\n", i);
  function();
  printf("function関数を呼び出した後のiの値は%d。\n", i);
}
// file2.c
static int i; // file1.cからは見えない。
static void function() // file1.cからは見えない。
{
  i = 2;
}
//例 staticの使用例2
void function() {
  static int i = 0; // staticを使うことで前の値が保持される。
  ++i;
  printf("iの値は%d。\n", i);
}
int main(void) {
  function();
  function();
  function();
}
autoはふつう省略する。
//例 autoの使用例
int main(void) {
    auto int i;//「int i;」と書いても同じである。
}
記憶域クラス指定子autoを伴って宣言されたブロック有効範囲の識別子は、自動記憶域期間をもつ。
registerを使うことで、レジスターを使う。
registerの使用例:
int main(void) {
  register int i, sum = 0;
  for (i = 1; i <= 100; i++)
    sum += i;
  printf("1から100までの総和は%d。\n", sum);
}
記憶域クラス指定子registerを用いて宣言されたオブジェクトは、
そのオブジェクトへのアクセスを可能な限り高速にすべきであることを示唆する。
この示唆が効果を持つ程度は、処理系定義とする。
また、registerを伴って宣言されたオブジェクトのどの部分のアドレスも、計算することはできない（記憶域クラス指定子registerを伴って宣言した変数には、アドレス演算子（ & ）を適用する事はできない）。
変数は一般にメモリー上に確保されるが、メモリーへのアクセスは比較的低速である。
一方、レジスターはCPU内部にあり、レジスターへのアクセスは比較的高速である。
なお、記憶域クラス指定子registerを用いたからといって必ず高速になるとは限らず、
また、用いなくともコンパイラーは自動的に生成コードを最適化するため低速になるとは限らない。
N2176 C17 ballot ISO/IEC 9899:2017 §6.7.2 ''Type specifiers''：型指定子。
構文
''
制約事項
少なくとも1つの型指定子は、各宣言の宣言指定子と、各構造体の宣言と型名の指定子修飾子リストで与えられなければならない。各構造体の宣言と型名の指定子修飾子リストで与えなければならない。型指定子の各リストは型指定子の各リストは、以下の多項式のうちの1つでなければならない（各項目に複数の多項式がある場合は、カンマで区切る）。型指定子はどのような順序でもよく、他の宣言型指定子と混在してもよい。
|-
| 64
| 64
| 64
| 64
| 64
| UNICOS
|}
はC99で導入された。
{{コラム|32ビット(x86)と64ビット(x64)|
以前のパソコンは32ビット(x86)を使うことが多かったが、
現在のパソコンは64ビット(x64)を使うことが主流となっているサーバ向けWindows OSでは Windows Server 2008 を最後に、PC向け Windows OSでは Windows 10 を最後に32ビットプロセッサのサポートを終了した。。
DLL(Dynamic Link Library)は、
32ビットと64ビットで互換性がないため、
特に注意が必要である。
Visual Studio Express 2012を使用する場合、
以下の手順で64ビットに切り替えることができる。
上部ツールバーの「Win32」で「構成マネージャー」を選択し、
「アクティブ ソリューション プラットホーム」で「新規作成」を選択し、
新しいプラットフォームに「x64」が選択されているのを確認し「OK」を選択する。
タスクマネージャーで64ビットのプロセスであることが確認できる。
-->
C99では、プログラムの移植性を高めるために、いくつかの新しい整数型が定義されています。 
すでに利用可能な基本的な整数型では、実際のサイズが実装によって定義され。システムによって異なる可能性があるため、（移植性を確保する上で）不十分であると考えられました。
新しい型は、ハードウェアが通常いくつかの型しかサポートしておらず、そのサポートが環境によって異なる組み込み環境において特に有用です。すべての新しい型は、 ヘッダー（及び、 ヘッダー）で定義されています。
型は、以下のカテゴリーに分類されます。
正確な幅の整数型: すべての実装で同じビット数 ''n'' を持つことが保証されている整数型（実装で利用可能な場合にのみ含まれます）。
最小幅の整数型: 少なくとも指定されたビット数 ''n'' を持つ、実装で利用可能な最小の型であることが保証されています。少なくとも N=8,16,32,64 で指定されていることが保証されます。
最速の整数型: 少なくとも指定されたビット数 ''n'' を持つ，実装で利用可能な最速の整数型であることが保証されています。少なくとも N=8,16,32,64 で指定されていることが保証されます。
ポインター整数型: ポインター保持できることが保証されている整数型（実装で利用可能な場合にのみ含まれます）。
最大幅の整数型: 実装上最大の整数型であることが保証されている整数型。
以下の表は、型と実装の詳細を取得するためのインターフェースをまとめたものです（''n''はビット数を意味します）。
列挙体( enumeration  )は、名前の付いた整数定数値( ''integer constant values'' )の集合で構成されています。それぞれの列挙は、異なる列挙型( enumerated type )を構成します。
ISO/IEC 9899:2017（通称 C17）の §6.7.2.2 ''Enumeration specifiers''（列挙型指定子）を抄訳/引用します。
構文
''
''
''
''§6.4.4.3 Enumeration constants
制約条件
列挙定数の値を定義する式は、intとして表現可能な値を持つ整数定数式でなければならない。
セマンティクス
列挙子リスト( ''enumerator-list'' )の識別子( ''identifier'' )は、int型の定数として宣言されており、許容される範囲内であればどこにでも現れることができます。
=を伴った持つ列挙子( ''enumerator'' )は、その列挙定数( ''enumeration-constant'' )を定数式( ''constant-expression'' )の値として定義します。
最初の列挙子に=がない場合、その列挙定数の値は 0 です。
=がない後続の各列挙子は、前の列挙定数の値に1を加えた定数式の値としてその列挙定数を定義します。
(=付きの列挙子を使用すると、同じ列挙内の他の値と重複する値を持つ列挙定数が生成されることがあります)。
列挙体の列挙子は、そのメンバーとしても知られています。
構文中で、列挙子リスト( ''enumerator-list'' )の後ろに , がないケースとあるケースの両方が書かれているので、どちらの書き方も許されます。
[https://paiza.io/projects/NXbg0pOVCrpf_eTHvTQq6w?language=cpp 列挙体の使用例]:
enum Day_of_the_Week {
  dwSUNDAY,
  dwMONDAY,
  dwTUESDAY,
  dwWEDNESDAY,
  dwTHURSDAY,
  dwFRIDAY,
  dwSATURDAY,
};
int weekend(enum Day_of_the_Week day) {
  switch (day) {
  case dwSUNDAY:
  case dwSATURDAY:
    return 1;
  case dwMONDAY:
  case dwTUESDAY:
  case dwWEDNESDAY:
  case dwTHURSDAY:
  case dwFRIDAY:
    return 0;
  }
}
int main(void) {
  const char * names[] = { // 順位を使った配列変数の初期化
    [dwSUNDAY] = "日曜日",
    [dwMONDAY] = "月曜日",
    [dwTUESDAY] = "火曜日",
    [dwWEDNESDAY] = "水曜日",
    [dwTHURSDAY] = "木曜日",
    [dwFRIDAY] = "金曜日",
    [dwSATURDAY] = "土曜日",
  };
  for (enum Day_of_the_Week dw = dwSUNDAY; dw <= dwSATURDAY; dw++) {
    printf("%i: %s(%s)\n", dw, names[dw], weekend(dw) ? "週末" : "平日");
  }
}
実行結果:
0: 日曜日(週末)
1: 月曜日(平日)
2: 火曜日(平日)
3: 水曜日(平日)
4: 木曜日(平日)
5: 金曜日(平日) 
6: 土曜日(週末)
列挙子（enumで定義する定数；しばしば列挙メンバーと呼ばれます）は、同じスコープでは別の列挙体でも同じ名前空間を使うので、衝突を避けるため列挙子に列挙体のニーモニックを前置するなどの対策が必要です。
ここでは、列挙子の名前が dw で始まるのはタグ Day_of_the_Week の列挙子であることを示しています。
列挙型の変数、dw はもちろん enum Day_of_the_Week 型であることを示しています。
この命名ルールは一例ですが、実際のプログラミングでは多くの識別子の名前を考える必要があるので、一貫性のある命名ルールを作ってコードを書くことで可読性・保守性が向上します。
Cのenumには、列挙子を集合で返したり、反復する機能も、列挙子の数を返す機能もないので、ループを廻すときは最初の列挙子から最後の列挙子までインクリメントしながらループすることになります（列挙子に初期値を与えている場合は、この手段も使えません）。
また、C++のコードとしてコンパイルすると、dw++ がエラーになります[https://paiza.io/projects/idW8CifxFGXZ5bG1sdRdWA?language=c static_cast を使えば]、dw = static_cast(dw + 1)コンパイルできますが、static_castは、コンパイラーの型チェックを骨抜きにしてしまうので、乱用は避けましょう（operator ++ をオーバーロードするにしても、その中で static_cast が必要）。。
ISO/IEC 9899:2017（通称 C17）の §6.7.2.3 ''Tags''（タグ）を抄訳/引用します。
制約事項
特定の型は、その内容が最大で一度だけ定義されなければならない。
それが指定する型が完成した後にのみ現れるものとする。
セマンティクス
同じスコープで同じタグを使用する struct、union、または enum の宣言はすべて同じスコープを持ち、同じタグを使用する struct、union、 enum のすべての宣言は、同じ型を宣言する。
型は、内容を定義するリストの閉じ括弧までは不完全で、それ以降は完全な型となります。
struct、union、 enum の宣言のうち、スコープが異なるものや、異なるタグを使用しているものが2つあると、異なる型を宣言することになります。
異なるスコープまたは異なるタグを使用する2つの struct、union、 enum の宣言は、異なる型を宣言します。
struct、union、または enum の各宣言でタグを含まない struct、union、 enum の宣言は、それぞれ個別の型を宣言します。
struct-or-union identifieropt { struct-declaration-list }。
は、 struct、union、または enum を宣言します。
リストは、struct のコンテンツ、union のコンテンツ、または enum のコンテンツを定義します。
識別子が提供されている場合、型指定子は識別子をその型のタグであるとも宣言する。
型修飾子( ''Type qualifiers'' )は次のいずれかの組み合わせです.。
構文
''
制約事項
参照される型がオブジェクト型であるポインター型以外の型は、制限修飾してはならない。
実装がアトミック型をサポートしていない場合、_Atomic修飾子は使用してはならない(6.10.8.3 ''Conditional feature macros'' 参照）。
_Atomic修飾子によって変更される型は、配列型や関数型であってはならない。
const修飾型をもったオブジェクトは初期化以外で変更不可能となります。
つまり、そのオブジェクトを不変値( ''constant value'' )として扱うことができ、言語処理系に畳み込みなどの最適化の機会を与えます。
const修飾型をもったオブジェクトは初期化が必須になります。
例、次の宣言のペアは、「不変値への可変ポインター」と「可変値への不変ポインター」の違いを示しています。
const int *ptr̲to̲constant;   
int *const constant̲ptr; 
ptr_to_constant が指すオブジェクトの内容は、そのポインターを介して変更してはならないが、ptr_to_constant 自体は別のオブジェクトを指すように変更することができる。
同様に、constant_ptr が指すintの内容は変更されても構いませんが、constant_ptr 自体は常に同じ場所を指すものとします。
[https://paiza.io/projects/YJ3KIZyACeAfTbJzglHoyA?language=c constの誤った使用例]:
int main(void) {
  const int i = 1;
  i++; // iの値は変更不可能なので、この箇所でコンパイルエラーとなります。
}
コンパイル結果:
Main.c:3:4: error: cannot assign to variable 'i' with const-qualified type 'const int'
  i++; // iの値は変更不可能なので、この箇所でコンパイルエラーとなります。
  ~^
Main.c:2:13: note: variable 'i' declared const here
  const int i = 1;
  ~~~~~~~~~~^~~~~ 
1 error generated.
restrictとは、ポインターに用いる型修飾子であり、そのポインターが指す先を、同一関数・ブロック内の別のポインターが指さないという情報をコンパイラーに伝え、コンパイラーの最適化を促進することができます。
restrict型修飾子を付けても付けなくても、目に見える動作は変わりません。
restrictの例として、標準ライブラリstring.hのmemcpy関数とmemmove関数が挙げられる。
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
これら2つの関数は、どちらもs2をs1にn文字コピーしますが、memcpyは領域の重なり合わないオブジェクト間でコピーする必要があり、
そのためrestrict型修飾子を用いることができます。
volatileとは、変数がコンパイラーに未知の方法で変更され、又はその他の未知の副作用を持つことをコンパイラーに伝え、コンパイラーの最適化を抑制する型修飾子です。
volatileで型修飾する動機として：
などがあります。
これらのオブジェクトの参照が共通部分式削除や定数畳み込みなどの言語処理系による畳込みでループの最初に一度だけ評価されたり、あるいは全く評価されなくなることを避ける狙いがあります。
ただし、
など、volatileで型修飾する動機になったニーズを完全に充足しているかは検討の余地があります。
原子型指定子( ''Atomic type specifier''s )は、スレッドとともにC11で導入されました。
構文
制約事項
実装が原子型をサポートしていない場合、原子型指定子( ''Atomic type specifiers'' )を使用してはならない（6.10.8.3 Conditional feature macros 参照）。
原子型指定子の型名は、配列型、関数型、原子型、または修飾型を参照してはならない。
セマンティクス
原子型に関連するプロパティは、lvalues（左辺値）である式に対してのみ意味を持ちます。
キーワード_Atomicの直後に左括弧がある場合は、型修飾子ではなく、（型名を持つ）型指定子として解釈されます。