サーチ…
備考
オブジェクトまたはファンクションを参照する識別子の宣言は、単純にオブジェクトまたはファンクションの宣言とも呼ばれます。
別のCファイルから関数を呼び出す
foo.h
#ifndef FOO_DOT_H /* This is an "include guard" */
#define FOO_DOT_H /* prevents the file from being included twice. */
/* Including a header file twice causes all kinds */
/* of interesting problems.*/
/**
* This is a function declaration.
* It tells the compiler that the function exists somewhere.
*/
void foo(int id, char *name);
#endif /* FOO_DOT_H */
foo.c
#include "foo.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync. Put this
* header first in foo.c to ensure the header is self-contained.
*/
#include <stdio.h>
/**
* This is the function definition.
* It is the actual body of the function which was declared elsewhere.
*/
void foo(int id, char *name)
{
fprintf(stderr, "foo(%d, \"%s\");\n", id, name);
/* This will print how foo was called to stderr - standard error.
* e.g., foo(42, "Hi!") will print `foo(42, "Hi!")`
*/
}
main.c
#include "foo.h"
int main(void)
{
foo(42, "bar");
return 0;
}
コンパイルとリンク
最初に、 foo.c
とmain.c
両方をオブジェクトファイルに コンパイルし ます 。ここではgcc
コンパイラを使用しますが、コンパイラの名前が異なり、他のオプションが必要です。
$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c
ここでそれらをリンクして最終的な実行可能ファイルを生成します:
$ gcc -o testprogram foo.o main.o
グローバル変数の使用
グローバル変数の使用は一般的にお勧めできません。これにより、プログラムの理解が難しくなり、デバッグが難しくなります。しかし、時にはグローバル変数を使用することも許容されます。
global.h
#ifndef GLOBAL_DOT_H /* This is an "include guard" */
#define GLOBAL_DOT_H
/**
* This tells the compiler that g_myglobal exists somewhere.
* Without "extern", this would create a new variable named
* g_myglobal in _every file_ that included it. Don't miss this!
*/
extern int g_myglobal; /* _Declare_ g_myglobal, that is promise it will be _defined_ by
* some module. */
#endif /* GLOBAL_DOT_H */
global.c
#include "global.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync.
*/
int g_myglobal; /* _Define_ my_global. As living in global scope it gets initialised to 0
* on program start-up. */
main.c
#include "global.h"
int main(void)
{
g_myglobal = 42;
return 0;
}
extern
を使用してソースファイル間で変数を共有するにはどうすればよいですか?を参照してください。
グローバル定数の使用
ヘッダーは、例えば文字列テーブルのように、グローバルに使用される読み取り専用リソースを宣言するために使用できます。
それらを利用したいファイル(「 翻訳単位 」)に含まれる別個のヘッダーにそれらを宣言してください。同じヘッダーを使用して、関連する列挙を宣言してすべての文字列リソースを識別すると便利です:
resources.h:
#ifndef RESOURCES_H
#define RESOURCES_H
typedef enum { /* Define a type describing the possible valid resource IDs. */
RESOURCE_UNDEFINED = -1, /* To be used to initialise any EnumResourceID typed variable to be
marked as "not in use", "not in list", "undefined", wtf.
Will say un-initialised on application level, not on language level. Initialised uninitialised, so to say ;-)
Its like NULL for pointers ;-)*/
RESOURCE_UNKNOWN = 0, /* To be used if the application uses some resource ID,
for which we do not have a table entry defined, a fall back in
case we _need_ to display something, but do not find anything
appropriate. */
/* The following identify the resources we have defined: */
RESOURCE_OK,
RESOURCE_CANCEL,
RESOURCE_ABORT,
/* Insert more here. */
RESOURCE_MAX /* The maximum number of resources defined. */
} EnumResourceID;
extern const char * const resources[RESOURCE_MAX]; /* Declare, promise to anybody who includes
this, that at linkage-time this symbol will be around.
The 1st const guarantees the strings will not change,
the 2nd const guarantees the string-table entries
will never suddenly point somewhere else as set during
initialisation. */
#endif
実際に関連する.cファイルを作成したリソースを定義するには、関連するヘッダー(.h)ファイルで宣言されていたものの実際のインスタンスを保持する別の変換単位があります。
resources.c:
#include "resources.h" /* To make sure clashes between declaration and definition are
recognised by the compiler include the declaring header into
the implementing, defining translation unit (.c file).
/* Define the resources. Keep the promise made in resources.h. */
const char * const resources[RESOURCE_MAX] = {
"<unknown>",
"OK",
"Cancel",
"Abort"
};
これを使用するプログラムは次のようになります。
main.c:
#include <stdlib.h> /* for EXIT_SUCCESS */
#include <stdio.h>
#include "resources.h"
int main(void)
{
EnumResourceID resource_id = RESOURCE_UNDEFINED;
while ((++resource_id) < RESOURCE_MAX)
{
printf("resource ID: %d, resource: '%s'\n", resource_id, resources[resource_id]);
}
return EXIT_SUCCESS;
}
上の3つのファイルをGCCを使ってコンパイルし、それらをリンクしてmain
のプログラムファイルにします。
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(これらの-Wall -Wextra -pedantic -Wconversion
使用すると、コンパイラが本当に-Wall -Wextra -pedantic -Wconversion
になるので、コードを投稿する前に何かを見逃してはいけません。世界、あるいはプロダクションに配備する価値があります)
作成したプログラムを実行する:
$ ./main
そして、
resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'
前書き
宣言の例は次のとおりです。
int a; /* declaring single identifier of type int */
上記の宣言では、 int
型のオブジェクトを参照するa
という単一の識別子が宣言されa
います。
int a1, b1; /* declaring 2 identifiers of type int */
2番目の宣言では、 a1
とb1
という名前の2つの識別子が宣言されていますが、同じint
型でも他のオブジェクトを参照しています。
基本的には、このように動作します。まず、ある型を入れて、コンマ( ,
)で区切られた1つまたは複数の式を書きます( この時点では評価されません。このコンテキスト )。そのような式を書く際には、間接( *
)、関数呼び出し( ( )
)または添字(または配列インデックス - [ ]
)演算子のみをいくつかの識別子に適用することができます(演算子はまったく使用できません)。使用される識別子は、現在のスコープで表示する必要はありません。いくつかの例:
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | 説明 |
---|---|
1 | 整数型の名前。 |
2 | ある識別子z への間接参照を適用する評価されない式。 |
3 | 同じ宣言の中でもう1つの式が続くことを示すカンマがあります。 |
4 | 他の識別子x 間接参照を適用する評価されていない式。 |
5 | 式の値に間接参照を適用して、評価されていない式(*c) 。 |
6 | 宣言の終わり。 |
上記の識別子のどれもがこの宣言の前に見えなかったので、使用される式はその前に有効ではないでしょう。
そのような各式の後に、それで使用される識別子が現在のスコープに導入されます。 (識別子がそれにリンケージを割り当てている場合は、両方の識別子が同じオブジェクトまたは関数を参照するように同じタイプのリンケージで再宣言することもできます)
さらに、イコール演算子記号( =
)を初期化に使用することもできます。未評価の式(宣言子)の後に=
が宣言の内側に続く場合、導入される識別子も初期化されていると言います。 =
記号の後にもう一度式を入れることができますが、今回は評価され、その値は宣言されたオブジェクトの初期値として使用されます。
例:
int l = 90; /* the same as: */
int l; l = 90; /* if it the declaration of l was in block scope */
int c = 2, b[c]; /* ok, equivalent to: */
int c = 2; int b[c];
コードの後半では、新しく導入された識別子の宣言部分からまったく同じ式を書くことができ、すべてのアクセスに有効な値が割り当てられていると仮定して、宣言の先頭に指定された型のオブジェクトを与えます途中のオブジェクト。例:
void f()
{
int b2; /* you should be able to write later in your code b2
which will directly refer to the integer object
that b2 identifies */
b2 = 2; /* assign a value to b2 */
printf("%d", b2); /*ok - should print 2*/
int *b3; /* you should be able to write later in your code *b3 */
b3 = &b2; /* assign valid pointer value to b3 */
printf("%d", *b3); /* ok - should print 2 */
int **b4; /* you should be able to write later in your code **b4 */
b4 = &b3;
printf("%d", **b4); /* ok - should print 2 */
void (*p)(); /* you should be able to write later in your code (*p)() */
p = &f; /* assign a valid pointer value */
(*p)(); /* ok - calls function f by retrieving the
pointer value inside p - p
and dereferencing it - *p
resulting in a function
which is then called - (*p)() -
it is not *p() because else first the () operator is
applied to p and then the resulting void object is
dereferenced which is not what we want here */
}
b3
の宣言は、整数オブジェクトにアクセスするための平均値としてb3
値を使用する可能性があることを指定しています。
もちろん、 b3
間接( *
)を適用するには、適切な値も格納しておく必要があります(詳細はポインタを参照)。また、オブジェクトを取得する前に、まずオブジェクトに値を格納する必要があります(この問題については、 ここで詳しく見ることができます )。上記の例ではすべてこれを行っています。
int a3(); /* you should be able to call a3 */
これはコンパイラにa3
を呼び出そうとしていることを伝えます。この場合、 a3
はオブジェクトではなく関数を指します。オブジェクトと関数の1つの違いは、関数には常に何らかの連動があることです。例:
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
上記の例では、2つの宣言は同じ関数f2
参照していますが、2つの異なるブロックスコープを持つこのコンテキストでオブジェクトを宣言していた場合、2つの異なるオブジェクトになります。
int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */
今は複雑になっているようですが、演算子の優先順位を知っていれば、上の宣言を読むのに問題はありません。 *
演算子は( )
より優先度が低いため、かっこが必要です。
添え字演算子を使用する場合、結果として得られる式は実際には宣言の後で有効ではありません。なぜなら、その中で使用されるインデックス( [
および]
内の値)は常にこのオブジェクト/関数の最大許容値よりも1です。
int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */
しかし、5より小さい他のすべてのインデックスでアクセス可能でなければなりません。例:
a4[0], a4[1]; a4[4];
a4[5]
はUBとなる。配列の詳細については、 ここを参照してください 。
int (*a5)[5](); /* here a4 could be applied indirection
indexed up to (but not including) 5
and called */
残念なことに、私たちの場合、構文的には可能ですが、 a5
の宣言は現在の標準では禁止されています。
Typedef
typedef
はtypedef
というキーワードを先頭に、型の前にある宣言です。例えば:
typedef int (*(*t0)())[5];
( int typedef (*(*t0)())[5];
ように型の後に置くこともできint typedef (*(*t0)())[5];
が、これはお勧めしません )
上記の宣言は、typedef名の識別子を宣言します。後でこのように使うことができます:
t0 pf;
書いているのと同じ効果があります:
int (*(*pf)())[5];
見て分かるように、typedef名は、後で他の宣言に使用する型として宣言を「保存」します。この方法で、キーストロークを節約できます。また、 typedef
を使った宣言はまだ宣言されているので、上記の例だけでは制限されません:
t0 (*pf1);
同じです:
int (*(**pf1)())[5];
右または左のルールを使用してCの宣言を解読する
「右左」ルールは、C宣言を解読するための完全な規則です。また、作成にも役立ちます。
宣言で遭遇したシンボルを読む...
* as "pointer to" - always on the left side
[] as "array of" - always on the right side
() as "function returning" - always on the right side
ルールの適用方法
ステップ1
識別子を探します。これがあなたの出発点です。それからあなた自身に言う。「識別子は」。あなたは宣言を始めました。
ステップ2
識別子の右側にあるシンボルを見てください。 、たとえば、あなたが見つけた場合()
そこに、あなたは、この関数の宣言であることを知っています。だからあなたは"識別子が関数を返す"ことがあります。または、 []
が見つかった場合は、 "識別子は配列です"と言います。記号が足りなくなるか、右かっこを押すまで右に進みます)
。 (左括弧を打つと(
括弧の間に詰め物があっても、それは()
記号の始まりです。
ステップ3
識別子の左側にある記号を見てください。上記のシンボルのいずれかでない場合(たとえば、「int」のようなもの)、単にそれを言います。それ以外の場合は、上記の表を使用して英語に翻訳してください。記号が足りなくなるか、左括弧(
。
ここで宣言を作成するまで手順2と3を繰り返します。
ここではいくつかの例を示します。
int *p[];
まず、識別子を見つける:
int *p[];
^
"pは"
今度は、記号や右括弧が外れるまで右に移動してください。
int *p[];
^^
"pは配列"
もはや右に動くことができません(記号の外)ので、左に移動して見つけてください:
int *p[];
^
"pは"
左に行くと見つける:
int *p[];
^^^
"pはintへのポインタの配列です"。
(または"pは各要素がintへのポインタ型の配列です" )
もう一つの例:
int *(*func())();
識別子を探します。
int *(*func())();
^^^^
"funcは"
右に動く。
int *(*func())();
^^
"funcは関数を返す"
右括弧のために右に移動できないので、左に移動します。
int *(*func())();
^
"funcは、ポインタを"
左括弧のために左に移動することはできませんので、右に進んでください。
int *(*func())();
^^
"funcは関数へのポインタを返す関数です"
私たちはシンボルから外れているのでもう移動できませんので、左に行ってください。
int *(*func())();
^
"funcはポインタを返す関数へのポインタを返す関数です"
そして最後に、左に行ってください。右には何も残っていないからです。
int *(*func())();
^^^
"funcはintへのポインタを返す関数へのポインタを返す関数です"。
ご覧のとおり、このルールは非常に便利です。また、宣言を作成している間に自分自身を健全にチェックし、次のシンボルの配置場所とかっこが必要かどうかについてのヒントを与えることもできます。
いくつかの宣言は、プロトタイプ形式の配列サイズと引数リストのために、より複雑に見えます。 [3]
が表示されている場合は、 "array(size 3)of ..."と読み替えられます 。 (char *,int)
が "function expecting(char 、int)and returning ..."として読み込まれているのを見た場合。
ここは楽しいものです:
int (*(*fun_one)(char *,double))[9][20];
私はこのステップを解読するための各ステップを経ずに進めません。
* "fun_oneは、 (char 、double)を期待し、intの配列(size 20)の配列(size 9)へのポインタを返す関数へのポインタです。"
ご覧のように、配列のサイズと引数のリストを取り除くと、それほど複雑ではありません:
int (*(*fun_one)())[][];
そのように解読し、後で配列のサイズと引数のリストを入れることができます。
いくつかの最終的な言葉:
このルールを使って違法な宣言をすることはかなり可能です。したがって、Cの中で合法的なものについての知識が必要です。たとえば、上記の場合:
int *((*fun_one)())[][];
"fun_oneは、intへのポインタの配列の配列を返す関数へのポインタです"と読み込まれます 。関数は配列を返すことはできず、配列へのポインタだけを返すので、その宣言は不正です。
違法な組み合わせには以下のものがあります:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array
上記のすべてのケースでは、宣言が合法であるためには、これらの()
と[]
右側の記号の間の左の*
記号をバインドするための括弧が必要です。
ここにいくつかの例があります:
法的
int i; an int
int *p; an int pointer (ptr to an int)
int a[]; an array of ints
int f(); a function returning an int
int **pp; a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[]; a pointer to an array of ints
int (*pf)(); a pointer to a function returning an int
int *ap[]; an array of int pointers (array of ptrs to ints)
int aa[][]; an array of arrays of ints
int *fp(); a function returning an int pointer
int ***ppp; a pointer to a pointer to an int pointer
int (**ppa)[]; a pointer to a pointer to an array of ints
int (**ppf)(); a pointer to a pointer to a function returning an int
int *(*pap)[]; a pointer to an array of int pointers
int (*paa)[][]; a pointer to an array of arrays of ints
int *(*pfp)(); a pointer to a function returning an int pointer
int **app[]; an array of pointers to int pointers
int (*apa[])[]; an array of pointers to arrays of ints
int (*apf[])(); an array of pointers to functions returning an int
int *aap[][]; an array of arrays of int pointers
int aaa[][][]; an array of arrays of arrays of int
int **fpp(); a function returning a pointer to an int pointer
int (*fpa())[]; a function returning a pointer to an array of ints
int (*fpf())(); a function returning a pointer to a function returning an int
違法
int af[](); an array of functions returning an int
int fa()[]; a function returning an array of ints
int ff()(); a function returning a function returning an int
int (*pfa)()[]; a pointer to a function returning an array of ints
int aaf[][](); an array of arrays of functions returning an int
int (*paf)[](); a pointer to a an array of functions returning an int
int (*pff)()(); a pointer to a function returning a function returning an int
int *afp[](); an array of functions returning int pointers
int afa[]()[]; an array of functions returning an array of ints
int aff[]()(); an array of functions returning functions returning an int
int *fap()[]; a function returning an array of int pointers
int faa()[][]; a function returning an array of arrays of ints
int faf()[](); a function returning an array of functions returning an int
int *ffp()(); a function returning a function returning an int pointer
出典: http : //ieng9.ucsd.edu/~cs30x/rt_lt.rule.html