수색…
비고
객체 또는 함수를 참조하는 식별자의 선언은 단순히 객체 또는 함수의 선언으로 간단히 참조됩니다.
다른 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
실제로 관련 헤더 파일 (.h)에 선언 된 실제 인스턴스를 보유하는 또 다른 변환 단위 인 관련 .c 파일을 작성한 자원을 정의하려면 다음을 수행하십시오.
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;
}
위의 세 파일을 GCC를 사용하여 컴파일하고이를 링크하여 프로그램 파일 main
이됩니다. 예를 들면 다음과 같습니다 :
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(이러한 -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
라는 단일 식별자를 선언합니다.
int a1, b1; /* declaring 2 identifiers of type int */
두 번째 선언은 a1
과 b1
이라는 이름의 2 개의 식별자를 선언합니다.이 식별자는 동일한 int
유형을 사용하지만 다른 객체를 참조합니다.
기본적으로이 방식의 작동 방식은 다음과 같습니다. 먼저 어떤 유형 을 넣은 다음 쉼표 ( ,
)로 구분 된 하나 또는 여러 개의 표현식을 작성합니다 ( 이 시점에서 평가되지 않음 ). 그렇지 않으면 선언 자로 참조되어야합니다. 이 문맥 ). 이러한 표현식을 작성할 때 간접적 인 ( *
), 함수 호출 ( ( )
) 또는 첨자 (또는 배열 인덱싱 - [ ]
) 연산자 만 일부 식별자에 적용 할 수 있습니다 (연산자를 전혀 사용할 수도 없음). 사용 된 식별자는 현재 범위에서 볼 필요가 없습니다. 몇 가지 예 :
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | 기술 |
---|---|
1 | 정수 타입의 이름. |
2 | 일부 식별자 z 간접 참조를 적용하는 평가되지 않은 표현식입니다. |
삼 | 동일한 선언에서 하나 이상의 표현식이 계속됨을 나타내는 쉼표가 있습니다. |
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
는 객체 대신 함수를 참조합니다. 객체와 함수의 한 가지 차이점은 함수에는 항상 일종의 연결이 있다는 것입니다. 예 :
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
위의 예제에서 2 개의 선언은 동일한 함수 f2
참조하지만,이 문맥에서 (두 개의 다른 블록 범위가있는) 객체를 선언하는 경우 두 개의 서로 다른 객체가됩니다.
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];
와 같이 타입 뒤에 기술 할 수도 있지만 이것은 권장하지 않습니다 )
위의 선언은 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 단계
식별자의 오른쪽에있는 기호를보십시오. , 말한다면, 당신은 찾을 수 ()
다음이 함수에 대한 선언이다 것을 알고있다. 그러면 "식별자는 함수를 반환하는"것 입니다. 또는 []
을 찾으면 "identifier is array of" 라고 말할 것입니다. 당신은 귀하의 부족 또는 오른쪽 괄호를 명중 할 때까지 권리를 계속 )
. (왼쪽 괄호를 칠한 경우 (
괄호 사이에 물건이 있더라도 그 ()
기호의 시작 부분입니다. 아래에서 자세한 내용을 참조하십시오.)
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