
C プログラミング言語の構文は、C でソフトウェアを記述する際に適用される一連の規則です。この構文は、非常に簡潔で、結果として得られるオブジェクト コードと密接な関係を持ちながら、比較的高度なデータ抽象化を提供するプログラムを作成できるように設計されています。C は、移植可能なオペレーティング システム開発 で広く成功した最初の高水準言語でした。
データ構造
プリミティブデータ型
C プログラミング言語は、数値を整数、実数、複素数の 3 つの形式で表します。この区別は、ほとんどの中央処理装置の命令セットアーキテクチャにおける同様の区別を反映しています。整数データ型は数値を整数のセットに格納し、実数と複素数は実数のセットの数値 (または数値のペア) を浮動小数点形式で表します。
すべての C 整数型にはsigned、およびのunsignedバリアントがあります。signedまたはunsignedが明示的に指定されていない場合は、ほとんどの場合、signedが想定されます。ただし、歴史的な理由により、 plain は、およびとはchar異なる型です。これは、コンパイラと文字セットに応じて、符号付き型または符号なし型になります (C では、C 基本文字セットのメンバーが正の値を持つことが保証されています)。また、plain として指定されたビット フィールド型は、コンパイラに応じて、符号付きまたは符号なしになります。
signed charunsigned charint
整数型
C の整数型は固定サイズが異なり、さまざまな範囲の数値を表現できます。 型はchar1バイト(アドレス指定可能な最小の記憶単位) を占有し、通常は 8 ビット幅です ( charC の「基本」文字はすべて表現できますが、国際文字セットにはより広い型が必要になる場合があります)。ほとんどの整数型には、およびキーワードで指定される符号付きと符号なしの両方の種類があります。符号付き整数型は、 C23 [1]以降、常に2 の補数表現を使用します(実際はそれ以前も。C23 より前の古い C バージョンでは、表現は1 の補数または符号と絶対値になることもありましたが、実際のところ、現代のハードウェアでは数十年にわたってそうではありませんでした)。多くの場合、型を指定する同等の方法が複数あります。たとえば、と は同義です。
signedunsigned signed short intshort
一部の型の表現には、未使用の「パディング」ビットが含まれる場合があります。このビットはストレージを占有しますが、幅には含まれません。次の表は、標準の整数型と、その最小許容幅 (符号ビットを含む) の完全なリストを示しています。
型はと のchar両方とは異なりますが、どちらか一方と同じ表現を持つことが保証されています。および型は 1999 年から標準化されており、古い C コンパイラではサポートされていない可能性があります。 型は通常、標準ヘッダー で定義された名前を介してアクセスされます。
signed charunsigned char_Boollong long_Booltypedefboolstdbool.h
一般に、特定のプラットフォーム用に実装される幅と表現方式は、マシン アーキテクチャに基づいて選択されますが、他のプラットフォーム用に開発されたソース コードのインポートの容易さも考慮されます。型の幅は、intC 実装間で特に大きく異なります。多くの場合、特定のプラットフォームの最も「自然な」ワード サイズに対応します。標準ヘッダーlimits.h は、特定のプラットフォームで実装される標準整数型の最小および最大の表現可能な値のマクロを定義します。
標準の整数型に加えて、標準ヘッダーの s に使用できる他の「拡張」整数型が存在する場合があります。幅をより正確に指定するには、プログラマーは標準ヘッダーstdint.hの s を使用できます。
typedefまた、使用する必要があります。typedef
整数定数は、ソース コード内でいくつかの方法で指定できます。数値は、10 進数(例: 1022)、プレフィックスとしてゼロ ( ) を使用した8 進数( )、またはプレフィックスとして(ゼロ x)を使用した16 進数( ) として指定できます。一重引用符で囲まれた文字 (例: ) は「文字定数」と呼ばれ、実行文字セット内のその文字の値 ( 型 ) を表します。文字定数を除き、整数定数の型は、指定された値を表すために必要な幅によって決まりますが、常に 以上の幅になります。これは、明示的な長さや符号修飾子を追加することでオーバーライドできます。たとえば、は 型 です。負の整数定数はありませんが、単項否定演算子 " " を使用すると、多くの場合同じ効果が得られます。
0017760x0x3FE'R'intint12luunsigned long-
列挙型
C の列挙型は、キーワードで指定されenum、単に「enum」(通常、 / ˈ iː n ʌ m / EE -numまたは/ ˈ iː n uː m / EE -noomと発音) と呼ばれることが多く、一連の名前付き定数にわたって値を表すために設計された型です。列挙定数のそれぞれには型がありますint。各enum型自体は または符号付きまたは符号なしの整数型と互換性がありますcharが、実装ごとに型を選択するための独自のルールが定義されています。
一部のコンパイラでは、列挙型のオブジェクトに定数以外の値が割り当てられると警告が出されます。ただし、このようなオブジェクトには互換性のある型の範囲内で任意の値を割り当てることができ、整数が期待される場所でenumあればどこでも定数を使用できます。このため、プリプロセッサディレクティブの代わりに値を使用して、名前付き定数を作成することがよくあります。このような定数は特定の識別子名前空間内に存在するため、通常はマクロよりも安全に使用できます。
enum#define
列挙型は、enum指定子と列挙型のオプション名 (またはタグ) で宣言され、その後に中括弧で囲まれカンマで区切られた 1 つ以上の定数のリストと、オプションの変数名のリストが続きます。特定の列挙型への後続の参照enumでは、キーワードと列挙型の名前が使用されます。デフォルトでは、列挙型の最初の定数に値 0 が割り当てられ、後続の各値は前の定数より 1 ずつ増加します。宣言内の定数に特定の値を割り当てることもできます。特定の値を持たない後続の定数には、その時点から増加した値が与えられます。たとえば、次の宣言を考えてみましょう。
enum colors { RED 、GREEN 、BLUE = 5 、YELLOW } paint_color ;
これはenum colors、型、int定数RED(値は 0)、GREEN(値は より 1 大きいRED1)、BLUE(値は指定された値 5)、YELLOW(値は より 1 大きいBLUE6)、およびenum colors変数を宣言しますpaint_color。定数は (任意の整数値が許可される)のコンテキストの外部で使用することができ、定数以外の値を、または 型の他の変数enumに割り当てることができます。
paint_colorenum colors
浮動小数点型
浮動小数点形式は、小数部を含む数値を表すために使用されます。ただし、ほとんどの有理数を正確に表すことはできません。代わりに、近似値になります。実数値には、指定子で示される 3 つの標準型 ( C23以降ではさらに 3 つの 10 進型) があります。単精度 ( float)、倍精度 ( double)、および拡張倍精度 ( long double) です。これらはそれぞれ異なる形式で値を表す場合があり、多くの場合、IEEE 浮動小数点形式のいずれかになります。
浮動小数点定数は、10 進表記で記述できます1.23。たとえば、 。10 進科学的表記法は、 または の後に 10 進指数 ( E 表記とも呼ばれます) を追加するeことによって使用できます。たとえば、 (値は 1.23 × 10 2 = 123.0 になります)です。小数点または指数のいずれかが必要です (そうでない場合、数値は整数定数として解析されます)。16進浮動小数点定数は同様のルールに従いますが、 を接頭辞として付け、またはを使用して2 進指数を指定します。たとえば、 (A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4なので、値は 2.5 になります)。10 進および 16 進浮動小数点定数はどちらも、型の定数であることを示すためにまたはを接尾辞として付けたり、型であることを示すために(文字) または を付けたり、または を接尾辞として付けずに定数にすることができます。
E1.23e20xpP0xAp-2fFfloatllLlong doubledouble
標準ヘッダー ファイルは、float.h実装の浮動小数点型、、の最小値と最大値を定義しますfloat。doubleまたlong double、浮動小数点数の処理に関連するその他の制限も定義します。
C23 では、_Decimal32、_Decimal64、_Decimal128 という 3 つの追加の 10進数 (2 進数ではなく) 実数浮動小数点型が導入されています。
- 注:Cではfloat、double、long doubleの基数は指定されていません。実装ではfloat、double、long doubleの表現を10進浮動小数点型と同じにすることができます。[2]
それにもかかわらず、基数は歴史的にバイナリ (基数 2) であるため、1/2 や 1/4 などの数値は正確ですが、1/10、1/100、1/3 などの数値は正確ではありません。10 進浮動小数点では、同じ数値すべてが正確ですが、1/10 や 1/100 などの数値は正確ですが、1/3 などの数値は正確ではありません。これまでバイナリ型として知られていたものに対して、10 進基数を選択する実装は知られていません。ほとんどのコンピューターは 10 進型用のハードウェアさえ持っていないため、それを持っている少数のコンピューター ( IBM System z10以降の IBM メインフレームなど) は、明示的に 10 進型を使用できます。
ストレージクラス指定子
すべてのオブジェクトにはストレージ クラスがあります。これは、最も基本的なストレージ期間を指定します。ストレージ期間は、静的 (グローバルのデフォルト)、自動 (ローカルのデフォルト)、または動的 (割り当て済み) であり、その他の機能 (リンケージおよびレジスタ ヒント) も指定します。
- 1
malloc()およびライブラリ関数を使用して割り当ておよび解放されますfree()。
ブロック内で宣言された変数は、デフォルトで自動ストレージを持ちます。 [注 2]またはストレージ クラス指定子を使用して明示的に宣言された変数も同様です。および指定子は、関数と関数引数宣言内でのみ使用できます。そのため、指定子は常に冗長です。すべてのブロックの外側で宣言されたオブジェクトと、ストレージ クラス指定子を使用して明示的に宣言されたオブジェクトには、静的ストレージ期間があります。 静的変数は、デフォルトでコンパイラによってゼロに初期化されます。
autoregisterautoregisterautostatic
自動ストレージを持つオブジェクトは、宣言されたブロックに対してローカルであり、ブロックの終了時に破棄されます。さらに、ストレージ クラスで宣言されたオブジェクトは、コンパイラによってレジスタregisterへのアクセスに対して高い優先順位が与えられる場合がありますが、コンパイラは実際にはそれらのオブジェクトをレジスタに格納しないことを選択する場合があります。このストレージ クラスのオブジェクトは、address-of ( ) 単項演算子で使用することはできません。静的ストレージを持つオブジェクトは、プログラムの実行期間中ずっと存続します。このようにして、関数は複数の呼び出しにわたって同じオブジェクトにアクセスできます。割り当てられたストレージ期間を持つオブジェクトは、、、および関連関数を使用して明示的に作成および破棄されます。
&mallocfree
ストレージexternクラス指定子は、オブジェクトのストレージが他の場所で定義されていることを示します。ブロック内で使用すると、ストレージがそのブロック外の宣言によって定義されていることを示します。すべてのブロック外で使用すると、ストレージがコンパイル単位外で定義されていることを示します。externストレージ クラス指定子は、関数宣言で使用する場合は冗長です。宣言された関数がコンパイル単位外で定義されていることを示します。
C11で導入された ( C++ では、C_Thread_localではC23 以降、ヘッダーが含まれている場合はそれ以前のバージョンの C では) ストレージ クラス指定子は、スレッド ローカル変数を宣言するために使用されます。またはと組み合わせてリンクを決定できます。
thread_local<threads.h>staticextern
ストレージ指定子は関数とオブジェクトにのみ適用されることに注意してください。型や列挙型の宣言などの他のものは、それが出現するコンパイル単位に対してプライベートです。一方、型には修飾子があります (以下を参照)。
型修飾子
型は、データの特別な特性を示すために修飾することができます。型修飾子は、const値が初期化されると変更されないことを示します。修飾constされた値を変更しようとすると未定義の動作が発生するため、一部の C コンパイラはそれらをrodataまたは (組み込みシステムの場合)読み取り専用メモリ(ROM) に格納します。型修飾子は、最適化コンパイラvolatileに対して、明らかに冗長な読み取りまたは書き込みを削除しない可能性があることを示します。これは、値が式または文によって変更されていない場合でも変更される場合や、メモリ マップ I/Oなどのように複数の書き込みが必要になる場合があるためです。
不完全な型
不完全な型とは、メンバーがまだ指定されていない構造体または共用体型、次元がまだ指定されていない配列型、またはvoid型 (void型を完了できない) です。このような型はインスタンス化できません (サイズが不明)。また、メンバーにアクセスできません (メンバーも不明です)。ただし、派生したポインター型は使用できます (ただし、逆参照はできません)。
これらは、前方宣言または外部宣言として、ポインターとともに使用されることがよくあります。たとえば、コードでは次のように不完全な型を宣言できます。
構造体 thing * pt ;
これは、不完全な型へptのポインターとして宣言します。データへのポインターは、指しているものに関係なく常に同じバイト幅を持つため、このステートメントはそれ自体で有効です ( が逆参照されない限り)。不完全な型は、後で同じスコープ内で再宣言することで完了できます。
struct thing struct thingpt
struct thing { int num ; }; /* thing 構造体型が完成しました */
不完全な型は再帰構造を実装するために使用されます。型宣言の本体は、翻訳単位の後半に延期される場合があります。
typedef struct Bert Bert ; typedef struct Wilma Wilma ;
構造体Bert { Wilma * wilma ; };
構造体Wilma { Bert * bert ; };
不完全な型はデータの隠蔽にも使用されます。不完全な型はヘッダー ファイルで定義され、本体は関連するソース ファイル内でのみ定義されます。
ポインタ
宣言では、アスタリスク修飾子 ( *) はポインター型を指定します。たとえば、指定子がint整数型を参照する場合、指定子はint*「整数へのポインター」型を参照します。ポインター値は、メモリ アドレスとデータ型の 2 つの情報を関連付けます。次のコード行は、ptrと呼ばれる整数へのポインター変数を宣言します。
ポインタ;
参照
非静的ポインターが宣言されると、それに関連付けられた未指定の値を持ちます。このようなポインターに関連付けられたアドレスは、使用する前に割り当てによって変更する必要があります。次の例では、ptr は変数aに関連付けられたデータを指すように設定されています。
int a = 0 ; int * ptr = &a a ;
これを実現するために、「アドレス」演算子 (単項&) が使用されます。この演算子は、後続のデータ オブジェクトのメモリ位置を生成します。
逆参照
指し示されたデータには、ポインター値を介してアクセスできます。次の例では、整数変数bは整数変数aの値、つまり 10 に設定されています。
整数a = 10 ;整数* p ; p = &a a ; int b = * p ;
このタスクを実行するには、アスタリスク (*) で示される単項逆参照演算子を使用します。この演算子は、オペランド (ポインター型である必要があります) が指すデータを返します。したがって、式 * p はaと同じ値を表します。ヌル ポインターの逆参照は無効です。
配列
配列の定義
C では、配列は同じ型の連続した要素の構造を表すために使用されます。(固定サイズの) 配列の定義には次の構文があります。
整数配列[ 100 ];
これは、プリミティブ型 の 100 個の値を保持するarrayintという名前の配列を定義します。関数内で宣言されている場合、配列の次元は非定数式である可能性があり、その場合、指定された数の要素のメモリが割り当てられます。後で使用するほとんどのコンテキストでは、変数arrayの記述は、配列の最初の項目へのポインタに変換されます。sizeof演算子 は例外です。 は、sizeof array配列全体のサイズ (つまり、 のサイズの 100 倍) を生成しint、sizeof(array) / sizeof(int)100 を返します。もう 1 つの例外は & (アドレス) 演算子で、配列全体へのポインタを生成します。たとえば、
int ( * ptr_to_array )[ 100 ] = &配列;
要素へのアクセス
配列の要素の値にアクセスするための主な機能は、配列の添え字演算子です。配列のiインデックス要素にアクセスするには、構文は となり、これはその配列要素に格納されている値を参照します。
array[i]
配列の添え字の番号は 0 から始まります (ゼロベースのインデックスを参照)。したがって、配列の添え字の最大許容値は、配列内の要素数から 1 を引いた数になります。これを説明するために、10 個の要素を持つと宣言された配列aを考えてみましょう。最初の要素は でa[0]、最後の要素は ですa[9]。
C には、配列の使用時に境界を自動的にチェックする機能はありません。論理的には 10 要素の配列の最後の添え字は 9 になりますが、添え字 10、11 などが誤って指定され、未定義の結果が生じる可能性があります。
配列とポインタは互換性があるため、配列の各要素のアドレスは同等のポインタ演算で表現できます。次の表は、既存の配列の両方の方法を示しています。
式はa[i]意味的には と等価であり*(a+i)、 は と等価であるため*(i+a)、式は と書くこともできますi[a]が、この形式はほとんど使用されません。
可変長配列
C99はブロックスコープ内で可変長配列(VLA)を標準化しました。このような配列変数は、実行時にブロックに入るときに整数値に基づいて割り当てられ、ブロックの終了時に解放されます。[3] C11以降、この機能はコンパイラによって実装する必要がなくなりました。
int n = ...; int a [ n ]; a [ 3 ] = 10 ;
この構文は、ブロックの最後までサイズが固定された配列を生成します。
動的配列
動的にサイズを変更できる配列は、 C 標準ライブラリの助けを借りて作成できます。malloc関数 は、メモリを割り当てるための簡単な方法を提供します。 1 つのパラメータ (割り当てるメモリの量 (バイト単位)) を取ります。 割り当てが成功すると、 は割り当てられた領域の先頭を指すmalloc汎用 ( void) ポインタ値を返します。 返されたポインタ値は、割り当てによって暗黙的に適切な型に変換されます。 割り当てを完了できなかった場合は、 はnull ポインタmallocを返します。 したがって、次のセグメントは、上記の目的の宣言と機能的に似ています。
#include <stdlib.h> /* malloc を宣言 */ ... int * a = malloc ( n * sizeof * a ); a [ 3 ] = 10 ;
結果は、n 個の連続するオブジェクトの最初のオブジェクトを指す「ポインターint」変数 ( a ) です。配列ポインターは等価であるため、最後の行に示すように、実際の配列名の代わりにこれを使用できます。この動的割り当てを使用する利点は、割り当てられるメモリの量を実行時に実際に必要な量に制限でき、必要に応じて変更できることです (標準ライブラリ関数を使用)。
intrealloc
動的に割り当てられたメモリが不要になった場合は、ランタイム システムに解放する必要があります。これは、free関数の呼び出しによって行われます。この関数は、以前に割り当てられたメモリへのポインターという 1 つのパラメーターを受け取ります。これは、 の以前の呼び出しによって返された値ですmalloc。
セキュリティ対策として、一部のプログラマー[誰? ]はポインタ変数を次のように設定しますNULL。
free ( a );
a = NULL ;
これにより、ほとんどのシステムで、ポインターをさらに逆参照しようとすると、プログラムがクラッシュすることが保証されます。これが行われない場合、変数はぶら下がりポインターになり、解放後使用バグにつながる可能性があります。ただし、ポインターがローカル変数の場合、 に設定してもNULL、プログラムがポインターの他のコピーを使用することを防ぐことはできません。ローカルの解放後使用バグは、通常、静的アナライザーで簡単に認識できます。したがって、このアプローチはローカルポインターにはあまり役立ちませんが、長期間存続する構造体に格納されたポインターで使用されることが多くなります。ただし、一般的には、ポインターを に設定することはNULL良い方法です[誰によると? ]。これにより、プログラマーはNULL逆参照の前にポインターをチェックできるため、クラッシュを防ぐのに役立ちます。
配列の例を思い出すと、動的割り当てによって固定サイズの配列を作成することもできます。
int ( * a )[ 100 ] = malloc ( sizeof * a );
...配列へのポインターが生成されます。
配列へのポインターにアクセスするには、次の 2 つの方法があります。
( * a )[インデックス];
インデックス[ * a ];
反復処理は次の 2 つの方法でも実行できます。
( int i = 0 ; i < 100 ; i ++ ) ( * a )[ i ]の場合;
( int * i = a [ 0 ]; i < a [ 1 ]; i ++ ) * iの場合;
2 番目の例を使用する利点は、最初の例の数値制限が不要であることです。つまり、配列へのポインターは任意のサイズにすることができ、2 番目の例を変更せずに実行できます。
多次元配列
さらに、C は行優先順で格納される多次元配列をサポートしています。技術的には、C 多次元配列は、要素が配列である 1 次元配列にすぎません。多次元配列を宣言するための構文は次のとおりです。
int array2d [行数][列数];
ここで、ROWSとCOLUMNS は定数です。これは 2 次元配列を定義します。左から右に添字を読むと、array2d は長さROWSの配列で、各要素はCOLUMNS 個の整数の配列です。
この多次元配列の整数要素にアクセスするには、次のようにします。
配列2d [ 4 ][ 3 ]
再び、左から右に読むと、5 行目とその行の 4 番目の要素にアクセスします。式array2d[4]は配列であり、4 番目の整数にアクセスするために [3] で添え字付けされます。
高次元配列も同様の方法で宣言できます。
多次元配列を、配列へのポインターの配列 ( Iliffe ベクトルまたは配列の配列とも呼ばれる) と混同しないでください。前者は常に長方形 (すべてのサブ配列は同じサイズである必要があります) であり、連続したメモリ領域を占有します。後者はポインターの 1 次元配列であり、各ポインターはメモリ内の異なる場所にあるサブ配列の最初の要素を指す場合があり、サブ配列は同じサイズである必要はありません。後者は、 を複数回使用することで作成できますmalloc。
文字列
C では、文字列リテラルは二重引用符 ( ) で囲まれ"(例: )、文字列の終わりを示すnull 終了文字(0 値) コードが追加された指定された値"Hello world!"の配列にコンパイルされます。
char
文字列リテラルには改行文字を埋め込むことはできません。この禁止により、言語の解析がいくらか簡素化されます。文字列に改行文字を含めるには、\n以下のようにバックスラッシュ エスケープを使用できます。
charこのヌル終了形式を使用して
配列として編成された文字列データ (必ずしも定数ではない) を操作するための標準ライブラリ関数がいくつかあります。以下を参照してください。
C の文字列リテラル構文は非常に影響力があり、C++、Objective-C、Perl、Python、PHP、Java、JavaScript、C#、Ruby など、他の多くの言語にも取り入れられています。今日では、ほとんどすべての新しい言語が C スタイルの文字列構文を採用するか、それに基づいて構築されています。この構文を持たない言語は、C に先行する傾向があります。
バックスラッシュエスケープ
特定の文字はリテラル文字列式に直接含めることができないため、代わりにバックスラッシュ ( \) で始まるエスケープ シーケンスによって識別されます。たとえば、 のバックスラッシュは"This string contains \"double quotes\"."、(コンパイラに対して) 内側の引用符のペアが、文字列自体の区切り文字 (エンドポイント) としてデフォルトで読み取られるのではなく、文字列の実際の一部として意図されていることを示します。
バックスラッシュを使用すると、文字列にさまざまな制御文字などを入力できます。
他のバックスラッシュ エスケープの使用は C 標準では定義されていませんが、コンパイラ ベンダーは言語拡張として追加のエスケープ コードを提供することがよくあります。これらの 1 つは、 ASCII 16 進値 1B のエスケープ文字\eのエスケープ シーケンスです。これは、他の文字セット( EBCDICなど)での表現が不足しているため、C 標準には追加されませんでした。これは、 GCC、clang、およびtccで使用できます。
文字列リテラルの連結
C には文字列リテラルの連結があり、隣接する文字列リテラルはコンパイル時に連結されます。これにより、長い文字列を複数行に分割できるほか、C プリプロセッサ定義やマクロから生成された文字列リテラルをコンパイル時に文字列に追加することもできます。
printf ( __FILE__ ": %d: Hello " "world \n " , __LINE__ );
拡大します
printf ( "helloworld.c" ": %d: Hello " "world \n " , 10 );
これは文法的には次の文と同等である。
printf ( "helloworld.c: %d: Hello world \n " , 10 );
文字定数
個々の文字定数は一重引用符'A'で囲まれますint(例: char)。 は"A"2 つの文字 ('A' と '\0') のヌル終端配列を表すのに対し、 は'A'文字値 (ASCII を使用する場合は 65) を直接表します。 文字列の場合と同じバックスラッシュ エスケープがサポートされていますが、 は (もちろん)"エスケープしなくても文字として有効に使用できますが、'はエスケープする必要があります。
文字定数は空にできません (つまり、 は''無効な構文です)。ただし、文字列は空にできます (それでもヌル終端文字を持ちます)。複数文字定数 (例'xy') は有効ですが、めったに役に立ちません。複数文字定数を使用すると、整数に複数の文字を格納できます (例: 32 ビット整数には 4 つの ASCII 文字が収まり、64 ビット整数には 8 つの ASCII 文字が収まります)。文字が にパックされる順序はint指定されていない (定義は実装に任されている) ため、複数文字定数の移植可能な使用は困難です。
それでも、特定のプラットフォームとコンパイラの実装に限定された状況では、複数文字定数はシグネチャを指定する際に使用されます。一般的な使用例の 1 つは OSType です。これは、Classic Mac OS コンパイラとその固有のビッグエンディアン性の組み合わせにより、整数のバイトがリテラルで定義された文字の正確な順序で表示されることを意味します。一般的な「実装」による定義は実際には一貫しており、GCC、Clang、およびVisual C++では、ASCII で が'1234'生成されます。 [5] [6]0x31323334
文字列リテラルと同様に、文字定数もプレフィックスによって変更できます。たとえば、L'A'は型を持ちwchar_t、ワイド文字エンコーディングで "A" の文字値を表します。
ワイド文字列
型はchar1バイト幅なので、1つのchar値で表現できる文字コードは通常最大255種類で、世界中で使用されているすべての文字を表現するには十分ではありません。国際文字をより適切にサポートするために、最初のC標準(C89)では、ワイド文字(型でエンコードwchar_t)とワイド文字列が導入されました。これらは次のように記述されます。L"Hello world!"
ワイド文字は、一般的には 2 バイト ( UTF-16などの 2 バイト エンコーディングを使用) または 4 バイト (通常はUTF-32 ) のいずれかですが、標準 C では の幅が指定されておらずwchar_t、実装者に選択が委ねられています。Microsoft Windows では通常 UTF-16 が使用されるため、上記の文字列は Microsoft コンパイラでは 26 バイト長になります。Unixの世界では UTF-32 が好まれるため、GCC などのコンパイラでは 52 バイトの文字列が生成されます。2 バイト幅にはwchar_tと同じ制限があり、特定の文字 ( BMPchar外の文字) は単一の ; では表すことができず、サロゲート ペアを使用して表す必要があります。
wchar_t
オリジナルの C 標準では、ワイド文字の文字列を操作するための最小限の関数のみが指定されていましたが、1995 年に標準が修正され、char文字列のサポートに匹敵する、より広範なサポートが含まれるようになりました。関連する関数は、ほとんどの場合、対応する関数に「w」を追加するか、「str」を「wcs」に置き換えた名前が付けられています。これらは、ワイド文字の分類およびマッピング関数を含む
でchar指定されています。<wchar.h><wctype.h>
現在、国際文字をサポートするために一般的に推奨されている方法[注 3]は、配列に格納されるUTF-8を使用するもので、UTF-8は直接ASCII拡張charであるため、UTF-8エディタを使用している場合はソースコードに直接記述できます。
可変幅文字列
の一般的な代替手段は、可変幅エンコーディングをwchar_t使用することです。これにより、論理文字が文字列の複数の位置にまたがることができます。可変幅文字列は、コンパイラを混乱させるリスクを冒して、リテラルに逐語的にエンコードされるか、数値のバックスラッシュエスケープ(UTF-8 の "é"など)が使用されます。UTF -8エンコーディングは、標準ライブラリの文字列関数との互換性のために特別に設計されました(Plan 9に基づく)。エンコーディングのサポート機能には、埋め込まれたヌルがない、部分列の有効な解釈がない、および簡単な再同期が含まれます。これらの機能がないエンコーディングは、標準ライブラリ関数と互換性がない可能性があります。このような場合は、エンコーディング対応の文字列関数がよく使用されます。
"\xc3\xa9"
ライブラリ関数
文字列は、定数と変数の両方において、標準ライブラリを使用せずに操作できます。ただし、ライブラリには、ヌルで終了する文字列を操作するための 便利な関数が多数含まれています。
構造体と共用体
構造
C の構造体と共用体は、さまざまな型の名前付きメンバーのシーケンスで構成されるデータ コンテナーとして定義されます。これらは、他のプログラミング言語のレコードに似ています。構造体のメンバーはメモリ内の連続した場所に格納されますが、コンパイラは効率化のため、またはターゲット アーキテクチャによる適切な配置に必要なパディングとして、メンバー間またはメンバーの後にパディングを挿入できます (最初のメンバーの前には挿入できません) 。構造体のサイズは、メンバーのサイズの合計とパディングのサイズの合計に等しくなります。
労働組合
C の共用体は構造体に関連しており、異なるタイプとサイズのオブジェクトを (異なる時点で) 保持できるオブジェクトとして定義されます。他のプログラミング言語のバリアント レコードに似ています。構造体とは異なり、共用体のコンポーネントはすべてメモリ内の同じ場所を参照します。このように、共用体はさまざまな時点で異なるタイプのオブジェクトを保持するために使用でき、新しいタイプごとに個別のオブジェクトを作成する必要はありません。共用体のサイズは、その最大のコンポーネント タイプのサイズと同じです。
宣言
構造体は キーワードで宣言されstruct、共用体は キーワードで宣言されますunion。指定子キーワードの後には、構造体または共用体の形式を識別するために使用されるオプションの識別子名が続きます。識別子の後には、構造体または共用体の本体の宣言が続きます。これは、中括弧で囲まれたメンバー宣言のリストで、各宣言はセミコロンで終了します。最後に、宣言は、構造体または共用体のインスタンスとして宣言されるオプションの識別子名のリストで終わります。
たとえば、次の文は、s3 つのメンバーを含む という名前の構造体を宣言します。また、 と呼ばれる構造体のインスタンスも宣言しますtee。
構造体s { int x ; float y ; char * z ; } tee ;
また、次のステートメントは、 という名前の同様の共用体uと、 という名前のそのインスタンスを宣言しますn。
ユニオンu { int x ; float y ; char * z ; } n ;
構造体と共用体のメンバーは、不完全な型または関数型を持つことはできません。したがって、メンバーは宣言されている構造体または共用体のインスタンスになることはできません (その時点では不完全であるため) が、宣言されている型へのポインターになることができます。
構造体または共用体の本体が宣言され、名前が付けられると、必要に応じて指定子structまたはunionと名前を使用して、新しいデータ型と見なすことができます。たとえば、上記の構造体宣言が与えられた次の文は、sという名前の構造体の新しいインスタンスを宣言しますr。
構造体sr ;
また、構造体を後で参照するときにorキーワードがtypedef不要になるように指定子を使用するのも一般的です。構造体本体の後の最初の識別子は、構造体型の新しい名前として使用されます (構造体インスタンスはこのコンテキストでは宣言できません)。たとえば、次のステートメントは、何らかの構造体を含むs_typeという新しい型を宣言します。
structunion
typedef構造体{...} s_type ;
その後のステートメントでは、拡張された ... 指定子の代わりに指定子s_type をstruct使用して構造体を参照できます。
メンバーへのアクセス
メンバーには、構造体または共用体のインスタンスの名前、ピリオド ( .)、およびメンバーの名前を使用してアクセスします。たとえば、上記のteeの宣言がある場合、 y (型)と呼ばれるメンバーにはfloat、次の構文を使用してアクセスできます。
ティー. y
構造体は、通常、ポインターを介してアクセスされます。 ptr_to_teeと呼ばれるteeへのポインターを定義する次の例を考えてみましょう。
構造体s * ptr_to_tee = & tee ;
teeのメンバーy には、 ptr_to_tee を逆参照し、その結果を左オペランドとして使用すること でアクセスできます。
( * ptr_to_tee ) .y
これは、 ptr_to_tee がteeを指しているtee.y限り、上記のより単純な方法と同一です。演算子の優先順位(「.」は「*」よりも高い) のため、この目的には短い方法は不適切であり、代わりに として解析されるため、括弧が必要になります。この操作は一般的であるため、C ではポインターからメンバーに直接アクセスするための省略構文が提供されています。この構文では、インスタンスの名前がポインターの名前に置き換えられ、ピリオドが文字シーケンス に置き換えられます。したがって、 yにアクセスする次の方法は、前の 2 つと同一です。
*ptr_to_tee.y*(ptr_to_tee.y)->
ptr_to_tee -> y
組合のメンバーにも同じ方法でアクセスします。
これは連鎖させることができます。たとえば、リンク リストでは、n->next->next2 番目のノードを参照できます (がn->nextnull でないと仮定)。
割り当て
構造体と共用体の個々のメンバーに値を割り当てることは、他のオブジェクトに値を割り当てることと構文的には同じです。唯一の違いは、割り当ての左辺値が、上記の構文でアクセスされるメンバーの名前であることです。
構造体は、同じ型の別の構造体に単位として割り当てることもできます。構造体 (および構造体へのポインター) は、関数のパラメーターおよび戻り値の型として使用することもできます。
たとえば、次のステートメントは、上記の 構造体teeのxという名前のメンバーに 74 (文字 't' の ASCII コード ポイント) の値を割り当てます。
ティー。x = 74 ;
同じ割り当てをteeの代わりにptr_to_teeを使用すると、次のようになります。
ptr_to_tee -> x = 74 ;
組合員との割り当ては同一です。
その他の操作
C 標準によれば、構造体に対して実行できる正当な操作は、構造体のコピー、ユニットとしての割り当て (または初期化)、address-of ( &) 単項演算子によるアドレスの取得、およびそのメンバーへのアクセスのみです。共用体にも同じ制限があります。暗黙的に禁止されている操作の 1 つは比較です。構造体と共用体は、C の標準比較機能 ( ==、>、<など) を使用して比較することはできません。
ビットフィールド
C では、ビット フィールドと呼ばれる特別なメンバー タイプも提供されています。これは、ビット数が明示的に指定された整数です。ビット フィールドは、、、、または [注 4] 型の構造体 (または共用体) メンバーとして宣言され、intメンバー名の後にコロン ( ) と、そのメンバーが占有するビット数が続きます。1 つのビット フィールドの合計ビット数はsigned int、宣言された型の合計ビット数を超えてはなりません (ただし、C++ では、余分なビットがパディングに使用されるため、この制限は許可されています)。
unsigned int_Bool:
通常の C 構文規則の特別な例外として、またはintを指定せずに型 として宣言されたビット フィールドが符号付きか符号なしかは実装定義です。したがって、移植性のために、すべての構造体メンバーにまたは を明示的に指定することをお勧めします。
signedunsignedsignedunsigned
コロンとそれに続くビット数だけで構成される名前のないフィールドも許可されています。これらはパディングを示します。名前のないフィールドに幅 0 を指定すると、新しいワードへの配置が強制されます。 [7]共用体のすべてのメンバーは同じメモリを占有するため、幅 0 の名前のないビットフィールドは共用体では何も行いませんが、幅が 0 以外の名前のないビットフィールドは共用体に収まる必要があるため、共用体のサイズを変更することができます。
ビット フィールドのメンバーにはアドレスがないため、address-of ( &) 単項演算子で使用することはできません。このsizeof演算子はビット フィールドには適用できません。
次の宣言は、 と呼ばれる新しい構造体型fと、 と呼ばれるそのインスタンスを宣言しますg。コメントは各メンバーの説明を提供します。
struct f { unsigned int flag : 1 ; /* ビットフラグ: オン (1) またはオフ (0) のいずれかになります */ signed int num : 4 ; /* 符号付き 4 ビットフィールド。範囲は -7...7 または -8...7 です */ signed int : 3 ; /* 8 ビットに丸めるための 3 ビットのパディング */ } g ;
初期化
デフォルトの初期化は、上で説明したストレージ クラス指定子によって異なります。
言語の文法上、スカラー初期化子は任意の数の中括弧のペアで囲むことができます。ただし、ほとんどのコンパイラは、このようなペアが複数ある場合は警告を発します。
int x = 12 ; int y = { 23 }; // 合法、警告なしint z = { { 34 } }; // 合法、警告を期待する
構造体、共用体、配列は、初期化子リストを使用して宣言内で初期化できます。指定子が使用されない限り、初期化子のコンポーネントは、定義されて格納される順序で要素に対応するため、特定の要素の値の前に、先行するすべての値を指定する必要があります。指定されていない要素はゼロに設定されます (共用体を除く)。初期化値をあまり多く指定すると、エラーが発生します。
次のステートメントは、 piと呼ばれる構造体sの新しいインスタンスを初期化します。
構造体s { int x ; float y ; char * z ; };
構造体s pi = { 3 , 3.1415 , "Pi" };
指定初期化子
指定初期化子を使用すると、メンバーを名前で任意の順序で初期化でき、先行する値を明示的に指定する必要もありません。次の初期化は前の初期化と同じです。
構造体s pi = { . z = "Pi" , . x = 3 , . y = 3.1415 };
初期化子で指定子を使用すると、初期化「カーソル」が移動します。以下の例では、がMAX10 より大きい場合、 の中央にゼロ値の要素がいくつかありますa。が 10 より小さい場合、最初の 5 つの初期化子によって提供される値の一部が、2 番目の 5 つの初期化子によって上書きされます (がMAX5 より小さい場合、コンパイル エラーが発生します)。
int a [ MAX ] = { 1 , 3 , 5 , 7 , 9 , [ MAX -5 ] = 8 , 6 , 4 , 2 , 0 };
C89では、共用体は最初のメンバーに単一の値を適用して初期化されていました。つまり、上で定義した共用体u は、 int xメンバーのみを初期化できます。
ユニオンu値= { 3 };
指定初期化子を使用する場合、初期化されるメンバーは最初のメンバーである必要はありません。
ユニオンu値= { . y = 3.1415 };
配列のサイズが不明な場合 (つまり、配列が不完全な型である場合)、初期化子の数によって配列のサイズが決定され、その型は完全になります。
int x [] = { 0 , 1 , 2 } ;
複合指定子は、装飾のない初期化子リストが誤解される可能性がある場合に、明示的な初期化を行うために使用できます。以下の例では、wは構造体の配列として宣言され、各構造体はメンバーa(3 の配列int) とメンバーb( int) で構成されます。初期化子は のサイズをw2 に設定し、各 の最初の要素の値を設定しますa。
構造体{ int a [ 3 ], b ; } w [] = { [ 0 ]. a = { 1 }, [ 1 ]. a [ 0 ] = 2 };
これは次と同等です:
構造体{ int a [ 3 ], b ; } w [] = { { { 1 , 0 , 0 }, 0 }, { { 2 , 0 , 0 }, 0 } };
標準 C では初期化子の繰り返しを指定する方法はありません。
複合リテラル
初期化方法論を借用して複合構造と配列リテラルを生成することが可能です。
// 配列リテラルから作成されたポインター。int
* ptr = ( int [ ]){ 10 , 20 , 30 , 40 };
// 配列へのポインタ
。float ( * foo )[ 3 ] = & ( float []){ 0.5f , 1.f , -0.5f };
構造体s pi = (構造体s ){ 3 , 3.1415 , "Pi" };
複合リテラルは、宣言をより読みやすくするために、指定された初期化子と組み合わせられることが多い:[3]
pi = ( struct s ){ . z = "Pi" , . x = 3 , . y = 3.1415 };
オペレーター
制御構造
C は自由形式の言語です。
括弧のスタイルはプログラマーによって異なり、議論の対象となる場合があります。詳細については、 インデント スタイルを参照してください。
複合文
このセクションの項目では、任意の <statement> を複合ステートメントに置き換えることができます。複合ステートメントの形式は次のとおりです。
{
<オプション-宣言-リスト> <オプション-ステートメント-リスト> }
関数の本体として、または単一のステートメントが期待される任意の場所で使用されます。宣言リストは、そのスコープ内で使用される変数を宣言し、ステートメントリストは実行されるアクションです。括弧は独自のスコープを定義し、括弧内で定義された変数は閉じ括弧で自動的に割り当て解除されます。宣言とステートメントは、複合ステートメント内で自由に混在させることができます ( C++と同様)。
選択ステートメント
C には、 文と文の2 種類の選択文があります。
ifswitch
ステートメントifの形式は次のとおりです。
if ( <式> ) <文1 > else <文2 >
ステートメントではif、<expression>括弧内の がゼロ以外 (true) の場合、制御は に渡されます<statement1>。else句が存在し、 が<expression>ゼロ (false) の場合、制御は に渡されます<statement2>。else <statement2>部分はオプションであり、これが存在しない場合、false は<expression>を単にスキップすることになります<statement1>。 はelse常に、直前の一致しない に一致しますif。必要に応じて、または明確にするために、中括弧を使用してこれをオーバーライドできます。
ステートメントは、整数型である式switchの値に応じて、制御を複数のステートメントの 1 つに転送します。スイッチによって制御されるサブステートメントは通常、複合ステートメントです。サブステートメント内のステートメントには、キーワード、定数式、コロン (:) の順に構成された 1 つ以上のラベルを付けることができます。構文は次のとおりです。
casecase
switch ( <式> ) { case <ラベル1 > : <文1 > case <ラベル2 > : <文2 > break ; default : <文3 > }
同じスイッチに関連付けられたケース定数のうち、同じ値を持つものは 2 つありません。defaultスイッチに関連付けられるラベルは最大で 1 つです。ケース ラベルのいずれも、 の後の括弧内の式と等しくない場合はswitch、制御がdefaultラベルに渡されます。ラベルがない場合はdefault、実行は構造全体を過ぎたところから再開されます。
スイッチはネストできます。caseまたはラベルは、それを含むdefault最も内側のスイッチに関連付けられます。スイッチ ステートメントは「フォールスルー」できます。つまり、1 つのケース セクションの実行が完了すると、ステートメントはステートメントに遭遇するまで下方向に実行され続けます。フォールスルーは状況によっては便利ですが、通常は望ましくありません。前の例では、 に到達すると、ステートメントが実行され、中括弧内にはそれ以上何も実行されません。ただし、 に到達すると、 2 つのケース ステートメントを区切るものがないため、との両方が実行されます。
switchbreak;<label2><statements 2><label1><statements 1><statements 2>break
switch珍しいケースではあるが、ラベルを他の制御構造のサブブロックに挿入することも可能です。例としては、 DuffのデバイスやSimon TathamのPuttyにおけるコルーチンの実装などが挙げられます。[8]
反復文
C には 3 つの形式の反復文があります。
do
<文> while ( <式> ) ;
while ( <式> ) <文>
for ( <式> ; <式> ; <式> ) <文>
whileおよびステートメントでは、 の値が0 以外 (true に相当)doである限り、サブステートメントは繰り返し実行されます。 の場合、 のすべての副作用を含むテストは、各反復 ( の実行) の前に発生します。 の場合、テストは各反復の後に発生します。したがって、ステートメントは常にサブステートメントを少なくとも 1 回は実行しますが、 ではサブステートメントがまったく実行されない場合があります。
expressionwhile<expression><statement>dodowhile
声明:
( e1 ; e2 ; e3 )の場合;
は以下と同等です:
e1 ;
while ( e2 ) { s ;継続: e3 ; }
ただし、 文の動作は例外ですcontinue;(ループ内でではなく にforジャンプします)。 が空白の場合は、 に置き換える必要があります。
e3e2e21
ループ内の 3 つの式はいずれもfor省略できます。2 番目の式が省略されると、whileテストは常にゼロ以外になり、無限ループが発生する可能性があります。
C99以降、最初の式は宣言の形式を取ることができ、通常は次のように初期化子が含まれます。
for ( int i = 0 ; i < limit ; ++ i ) { // ... }
宣言のスコープはループの範囲内に制限されますfor。
ジャンプステートメント
ジャンプ ステートメントは、無条件に制御を転送します。Cには、、、の4 種類のジャンプ ステートメントがあります。
gotocontinuebreakreturn
声明gotoは次のようになります:
<識別子>に移動します;
識別子は、現在の関数内にあるラベル(コロンが続く) である必要があります。制御はラベルの付いたステートメントに移ります。
文continueは反復文の中にのみ出現し、最も内側の反復文のループ継続部分に制御を移します。つまり、各文の中で
while (式) { /* ... */ cont : ; }
do
{
/* ... */ cont : ; } while (式);
for (式1 ;式2 ;式3 ) { /* ... */続き: ; }
ネストされた反復ステートメント内に含まれていないはcontinueと同じですgoto cont。
このステートメントは、ループ、ループ、ループ、またはステートメントbreakを終了するために使用されます。制御は、終了したステートメントの次のステートメントに渡されます。
forwhiledoswitch
関数は、ステートメントによって呼び出し元に戻りますreturn。returnの後に式が続く場合、その値は関数の値として呼び出し元に返されます。関数の終わりに到達すると、return式のない と同等になります。その場合、関数が値を返すように宣言され、呼び出し元が返された値を使用しようとすると、結果は未定義になります。
ラベルのアドレスを保存する
GCC は、&&ラベルのアドレスを返す単項演算子を使用して C 言語を拡張します。このアドレスはvoid*変数型に格納でき、後で命令で使用できますgoto。たとえば、次のコードは"hi "無限ループで出力されます。
void * ptr = && J1 ;
J1 : printf ( "hi" ); goto * ptr ;
この機能を使用してジャンプ テーブルを実装できます。
機能
構文
C 関数の定義は、戻り値の型(void値が返されない場合)、一意の名前、括弧内のパラメータのリスト、およびさまざまなステートメントで構成されます。
<戻り値の型> functionName ( <パラメータリスト> ) { <ステートメント> return <戻り値の型の式> ; }
戻り値型以外の関数にはvoid、少なくとも 1 つのreturnステートメントを含める必要があります。パラメータは<parameter-list>、パラメータ宣言のコンマ区切りリストで指定されます。リスト内の各項目はデータ型で、その後に識別子が続きます: <data-type> <variable-identifier>, <data-type> <variable-identifier>, ...。
戻り値の型は配列型または関数型にすることはできません。
int f ()[ 3 ]; // エラー: 関数が配列を返しますint ( * g ())[ 3 ]; // OK: 関数が配列へのポインタを返します。
void h ()(); // エラー: 関数が関数を返すvoid ( * k ())(); // OK: 関数が関数ポインタを返す
パラメータがない場合、 は<parameter-list>空のままにするか、オプションで単一の単語 で指定することができますvoid。
データ型や変数識別子の代わりにキーワードを最後のパラメータとして指定することで、可変個のパラメータを取る関数を定義することができます。これを行う一般的な関数は、宣言が次のようになっている
...標準ライブラリ関数です。printf
int printf ( const char * , ...);
これらのパラメータの操作は、標準ライブラリ ヘッダー内のルーチンを使用して行うことができます<stdarg.h>。
関数ポインタ
関数へのポインタは次のように宣言できます。
<戻り値の型> ( *<関数名> )( <パラメータリスト> ) ;
次のプログラムは、加算と減算を選択するための 関数ポインターの使用を示しています。
#include <stdio.h>
int ( *演算)( int x , int y );
int add ( int x 、int y ) { return x + y ; }
int減算( int x 、int y ) {戻り値x - y ; }
int main ( int argc , char * args []) { int foo = 1 , bar = 1 ;
操作=加算; printf ( "%d + %d = %d \n " , foo , bar ,操作( foo , bar ));操作=減算; printf ( "%d - %d = %d \n " , foo , bar ,操作( foo , bar )); 0 を返す; }
グローバル構造
前処理の後、最高レベルでは、C プログラムはファイル スコープでの一連の宣言で構成されます。これらは複数の個別のソース ファイルに分割され、個別にコンパイルされることがあります。結果のオブジェクト モジュールは、実装によって提供されるランタイム サポート モジュールとともに リンクされ、実行可能イメージが生成されます。
宣言は関数、変数、型を導入します。C 関数はFortranのサブルーチンやPascalのプロシージャに似ています。
定義は特別なタイプの宣言です。変数定義はストレージを確保し、場合によっては初期化し、関数定義はその本体を提供します。
すべての標準ライブラリ関数を提供する C の実装は、ホストされた実装と呼ばれます。ホストされた実装用に作成されたプログラムでは、mainプログラムの実行開始時に最初に呼び出される と呼ばれる特別な関数を定義する必要があります。
ホストされた実装は、関数を呼び出すことによってプログラムの実行を開始します。関数はmain、次のいずれかのプロトタイプに従って定義する必要があります (異なるパラメータ名を使用したり、型のスペルを異なる方法で指定したりすることは許可されています)。
int main () {...} int main ( void ) {...} int main ( int argc , char * argv []) {...} int main ( int argc , char ** argv ) {...} // char *argv[] と char **argv は関数パラメータと同じ型です
最初の 2 つの定義は同等です (どちらも C++ と互換性があります)。どちらを使用するかは、おそらく個人の好み次第です (現在の C 標準には の例が 2 つmain()、 の例が 2 つ含まれていますmain(void)が、ドラフト C++ 標準では を使用していますmain())。 の戻り値main( のはずですint)は、ホスト環境に返される
終了ステータスとして機能します。
C 標準では、戻り値0と はEXIT_SUCCESS成功を示し、 はEXIT_FAILURE失敗を示すと定義されています (EXIT_SUCCESSと はEXIT_FAILUREで定義されています<stdlib.h>)。その他の戻り値の意味は実装定義です。たとえば、Linuxでは、シグナルによって強制終了されたプログラムは、シグナルの数値に 128 を加えた戻りコードを返します。
最小限の正しい C プログラムはmain、引数を取らず何もしない空のルーチンで構成されます。
int main ( void ){}
return文が存在しないため、main終了時に0を返します。[3] (これはC99で導入された特殊なケースの機能で、にのみ適用されますmain。)
通常、関数mainは、そのジョブを実行するために他の関数を呼び出します。
一部の実装はホストされません。通常、これらはオペレーティング システムで使用することを意図していないためです。このような実装は、C 標準ではフリースタンディングと呼ばれます。フリースタンディング実装では、プログラムの起動の処理方法を自由に指定できます。特に、関数を定義するためにプログラムを要求する必要はありませんmain。
関数はプログラマが作成することも、既存のライブラリによって提供されることもあります。後者のインターフェイスは通常、#include プリプロセッサ ディレクティブを使用してヘッダー ファイルをインクルードすることによって宣言され、ライブラリ オブジェクトは最終的な実行可能イメージにリンクされます。 などの特定のライブラリ関数はC 標準で定義されており、これらは標準ライブラリprintf関数と呼ばれます。
関数は呼び出し元 (通常は別の C 関数、または関数のホスト環境main) に値を返すことがあります。printf上記の関数は印刷された文字数を返しますが、この値は無視されることがよくあります。
引数の受け渡し
C では、引数は値によって関数に渡されますが、他の言語では変数は参照によって渡される場合があります。つまり、受信側の関数は値のコピーを取得し、元の変数を直接変更する方法はありません。関数が別の関数から渡された変数を変更するには、呼び出し側がそのアドレス(そのアドレスへのポインタ) を渡す必要があります。その後、受信側の関数でそのアドレスを逆参照できます。詳細については、「ポインタ」を参照してください。
void incInt ( int * y ) { ( * y ) ++ ; // 下の 'main' の 'x' の値を 1 増やします}
int main ( void ) { int x = 0 ; incInt ( & x ); // var 'x' への参照を渡すreturn 0 ; }
scanf関数も同様に動作します:
int x ; scanf ( "%d" , & x );
編集可能なポインターを関数に渡すには (割り当てられた配列を呼び出しコードに返す目的など)、そのポインターへのポインター (アドレス) を渡す必要があります。
#include <stdio.h> #include <stdlib.h>
void allocate_array ( int ** const a_p , const int A ) { /* A int の配列を割り当て、*a_p に割り当てると main() の 'a' が変更されます*/ * a_p = malloc ( sizeof ( int ) * A ); }
int main ( void ) { int * a ; /* 1つ以上のintへのポインタを作成します。これが配列になります */
/* 'a'のアドレスを渡す */
allocate_array ( &a a 、42 );
/* 'a' は長さ 42 の配列となり、ここで操作したり解放したりできます */
解放( a );
0を返す; }
パラメータはint **a_pへのポインタへのポインタであり、この場合は
main関数で定義されたintポインタのアドレスです。p
配列パラメータ
一見すると、配列型の関数パラメータは C の値渡しルールの例外のように見えるかもしれません。次のプログラムは 1 ではなく 2 を出力します。
#include <stdio.h>
void setArray ( int配列[], intインデックス, int値) {配列[インデックス] =値; }
int main ( void ) { int a [ 1 ] = { 1 }; setArray ( a , 0 , 2 ); printf ( "a[0]=%d \n " , a [ 0 ]); 0 を返す; }
ただし、この動作には別の理由があります。実際、配列型で宣言された関数パラメータは、ポインタとして宣言されたものと同じように扱われます。つまり、上記の の宣言は、setArray次の宣言と同等です。
void setArray ( int *配列、intインデックス、int値)
同時に、式で配列を使用する C の規則により、 のa呼び出しにおける の値は、setArray配列の最初の要素へのポインターに変換されますa。したがって、実際にはこれは依然として値渡しの例ですが、値渡しされるのは配列の内容ではなく、配列の最初の要素のアドレスであるという注意点があります。
C99 以降、プログラマーはキーワード を使用することで、関数が特定のサイズの配列を取るように指定できますstatic。最初のパラメーター は、長さが少なくとも 4 の配列の最初の要素へのポインターでなければなりません。配列が変換されるポインター型に修飾子 ( 、、 ) を括弧内に入れてvoid setArray(int array[static 4], int index, int value)追加することもできます。
constvolatilerestrict
匿名関数
無名関数は標準のCプログラミング言語ではサポートされていませんが、GCC [9]やClangなどの一部のC方言ではサポートされています。
湾岸協力会議
GNUコンパイラ コレクション(GCC) は、ネストされた関数とステートメント式が混在する匿名関数をサポートします。形式は次のとおりです。
( { return_type anonymous_functions_name (パラメータ) { function_body } anonymous_functions_name ; } )
次の例は GCC でのみ機能します。マクロの展開方法により、l_bodyには括弧の外側にカンマを含めることはできません。GCC はカンマをマクロ引数間の区切り文字として扱います。が使用可能なl_ret_type場合は引数を削除できます__typeof__。以下の例では、__typeof__配列で を使用すると が返されますtesttype *。必要に応じて、実際の値を参照できます。
#include <stdio.h>
//* これは匿名関数の定義です */
#define lambda(l_ret_type, l_arguments, l_body) \
({ \
l_ret_type l_anonymous_functions_name l_arguments \
l_body \
&l_anonymous_functions_name; \
})
#define forEachInArray(fe_arrType, fe_arr, fe_fn_body) \
{ \
int i=0; \
for(;i<sizeof(fe_arr)/sizeof(fe_arrType);i++) { fe_arr[i] = fe_fn_body(&fe_arr[i]); } \
}
typedef struct { int a ; int b ; }テストタイプ;
void printout ( const testtype * array ) { int i ; for ( i = 0 ; i < 3 ; ++ i ) printf ( "%d %d \n " , array [ i ]. a , array [ i ]. b ); printf ( " \n " ); }
int main ( void ) {テストタイプ配列[] = { { 0 , 1 }, { 2 , 3 }, { 4 , 5 } };
printout ( array );
/* 無名関数は foreach の関数として与えられます */ forEachInArray ( testtype , array , lambda ( testtype , ( void * item ), { int temp = ( * ( testtype * ) item ). a ; ( * ( testtype * ) item ). a = ( * ( testtype * ) item ). b ; ( * ( testtype * ) item ). b = temp ; return ( * ( testtype * ) item ); })); printout ( array ); return 0 ; }
Clang (C、C++、Objective-C、Objective-C++)
Clangはブロックと呼ばれる匿名関数をサポートしており、[10]その形式は以下のとおりです。
^ return_type (パラメータ) {関数本体}
上記のブロックのタイプは ですreturn_type (^)(parameters)。
前述のブロック拡張機能とGrand Central Dispatch (libdispatch) を使用すると、コードはよりシンプルになります。
#include <stdio.h> #include <dispatch/dispatch.h>
int main ( void ) { void ( ^ count_loop )() = ^ { for ( int i = 0 ; i < 100 ; i ++ ) printf ( "%d \n " , i ); printf ( "ah ah ah \n " ); };
/* 別の関数にパラメータとして渡す */
dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ), count_loop );
/* 直接呼び出す */
count_loop ();
0 を返す; }
-fblocks、-lBlocksRuntimeその他
予約キーワード
次の単語は予約されており、識別子として使用することはできません。
実装では、 などの他のキーワードを予約することもできますがasm、実装では通常、1 つまたは 2 つのアンダースコアで始まる非標準のキーワードが提供されます。
大文字と小文字の区別
C 識別子は大文字と小文字が区別されます (例: foo、FOO、 はFoo異なるオブジェクトの名前です)。一部のリンカーは外部識別子を単一の大文字と小文字にマップしますが、これは最近のほとんどのリンカーでは一般的ではありません。
コメント
トークン で始まるテキストはコメント/*として扱われ、無視されます。コメントは次の で終わります。コメントは式の中に出現したり、複数行にまたがったりすることができます。コメント ターミネータを誤って省略すると、次のコメントの適切に構築されたコメント ターミネータが最初のコメントを終了するために使用され、コメント間のすべてのコードがコメントと見なされるという問題が生じます。C スタイルのコメントはネストされません。つまり、誤ってコメント内にコメントを配置すると、予期しない結果が生じます。
*/
/*
この行は無視されます。
/*
ここでコンパイラ警告が生成される場合があります。これらの行も無視されます。
上記のコメント開始トークンは新しいコメントを開始しませんでした。
以下のコメント終了トークンは、1 行目に開始されたコメントを閉じます。
*/
この行とその下の行は無視されません。どちらもコンパイルエラーが発生する可能性があります。
*/
C++スタイルの行コメントは、 で始まり//、行末まで続きます。このスタイルのコメントはBCPLで始まり、 C99で有効な C 構文になりました。元の K&R C やANSI Cでは使用できません。
// この行はコンパイラによって無視されます
/* これらの行は
コンパイラによって 無視されます*/
x = * p /*q; /* このコメントは 'p' の後から始まります */
コマンドライン引数
コマンドラインで指定されたパラメータは、 内のコマンドライン引数の数と、ポインタ配列 内の文字列としての個々の引数という 2 つの定義済み変数を使用して C プログラムに渡されます。したがって、コマンド:
argcargv
マイフィルト p1 p2 p3
結果は次のようになります:
個々の文字列は連続した文字の配列ですが、文字列が連続したグループとして格納されるという保証はありません。
プログラムの名前 はargv[0]、診断メッセージを印刷する場合や、1 つのバイナリを複数の目的に使用したい場合に便利です。パラメータの個々の値には、次のプログラムに示すようにargv[1]、argv[2]、argv[3]、 でアクセスできます。
#include <stdio.h>
int main ( int argc , char * argv []) { printf ( "argc \t = %d \n " , argc ); for ( int i = 0 ; i < argc ; i ++ ) printf ( "argv[%i] \t = %s \n " , i , argv [ i ]); }
評価順序
ある程度複雑な式では、式の各部分を評価する順序について選択が生じます。 は、、、、の順序で評価されるか、、、、の順序で評価されます。形式的には、準拠する C コンパイラは、シーケンス ポイント間で任意の順序で式を評価できます(これにより、コンパイラは最適化を行うことができます)。 シーケンス ポイントは次のように定義されます。
(1+1)+(3+3)(1+1)+(3+3)(2)+(3+3)(2)+(6)(8)(1+1)+(3+3)(1+1)+(6)(2)+(6)(8)
- 文はセミコロンで終わります。
- シーケンス演算子:カンマ。ただし、関数の引数を区切るカンマはシーケンス ポイントではありません。
- 短絡演算子:論理AND ( 、これはthen と
&&読み取ることができます) および論理OR ( 、これはor else と読み取ることができます)。|| - 三項演算子( )
?:: この演算子は、最初に最初の部分式を評価し、次に最初の部分式の値に基づいて 2 番目または 3 番目 (両方ではない) を評価します。 - 関数呼び出しの開始と終了(ただし、引数の評価間は除く)。
シーケンス ポイントの前の式は、常にシーケンス ポイントの後の式より先に評価されます。短絡評価の場合、最初の式の結果によっては 2 番目の式が評価されないことがあります。たとえば、式 で、最初の引数が 0 以外 (true) と評価された場合、式全体の結果は true 以外にはならないため、は評価されません。同様に、式 で、最初の引数が 0 (false) と評価された場合、式全体の結果は false 以外にはならないため、は評価されません。
(a() || b())b()(a() && b())b()
関数呼び出しの引数は、関数に入るまでにすべて評価されていれば、任意の順序で評価できます。たとえば、次の式の動作は未定義です。
printf ( "%s %s \n " , argv [ i = 0 ], argv [ ++ i ]);
未定義の動作
C 標準の特徴 (C に固有のものではありません) として、特定のコードの動作が「未定義」であると言われることがあります。実際には、このコードから生成されたプログラムは、プログラマーの意図どおりに動作することから、実行するたびにクラッシュすることまで、何でもできることを意味します。
たとえば、次のコードでは、変数bがシーケンス ポイントを介さずに複数回変更される ため、未定義の動作が発生します。
#include <stdio.h>
int main ( void ) { int b = 1 ; int a = b ++ + b ++ ; printf ( "%d \n " , a ); }
「b ++ + b ++」では、 bの変更の間にシーケンス ポイントがないため、評価手順を複数の順序で実行して、あいまいなステートメントが発生する可能性があります。これは、コードを書き直してシーケンス ポイントを挿入し、あいまいでない動作を強制することで修正できます。次に例を示します。
a = b ++ ; a += b ++ ;
参照
- ブロック(C言語拡張)
- Cプログラミング言語
- C 変数の型と宣言
- C および C++ の演算子
- C標準ライブラリ
- C ファミリープログラミング言語(C の影響を受けた言語)の一覧
注記
参考文献
- ^ 「WG14-N2412: 2の補数符号表現」(PDF) . open-std.org . 2019年8月11日. 2022年12月27日時点のオリジナルよりアーカイブ(PDF) 。
- ^ 「WG14-N2341: ISO/IEC TS 18661-2 - C の浮動小数点拡張 - パート 2: 10 進浮動小数点演算」(PDF)。open -std.org。2019 年 2 月 26 日。2022 年 11 月 21 日時点のオリジナルよりアーカイブ(PDF)。
- ^ abc クレメンス、ベン(2012). 21世紀C.オライリーメディア. ISBN 978-1449327149。
- ^ Balagurusamy、E. ANSI C でのプログラミング。タタ・マグロウ・ヒル。 p. 366.
- ^ 「C プリプロセッサ: 実装定義の動作」gcc.gnu.org。
- ^ 「文字列リテラルと文字リテラル (C++)」。Visual C++ 19 ドキュメント。2019年11 月 20 日閲覧。
- ^ カーニハン&リッチー
- ^ Tatham, Simon (2000). 「C言語のコルーチン」2017年4月30日閲覧。
- ^ 「Statement Exprs (GNU コンパイラ コレクション (GCC) の使用)」。gcc.gnu.org 。2022 年 1 月 12 日閲覧。
- ^ 「ブロックの言語仕様 — Clang 13 ドキュメント」。clang.llvm.org 。2022 年 1 月 14 日閲覧。
- 一般的な
- カーニハン、ブライアン W. ;リッチー、デニス M. (1988)。プログラミング言語 C (第 2 版)。アッパー サドル リバー、ニュージャージー: プレンティス ホールPTR。ISBN 0-13-110370-9。
- アメリカ情報システム規格 - プログラミング言語 - C - ANSI X3.159-1989
- 「ISO/IEC 9899:2018 - 情報技術 - プログラミング言語 - C」。国際標準化機構。
- 「ISO/IEC 9899:1999 - プログラミング言語 - C」。Iso.org。2011-12-08。2014-04-08に閲覧。
外部リンク
- バッカス・ナウア記法によるCの構文
- C言語でのプログラミング
- comp.lang.c よくある質問ページ
