수색…


비고

객체 또는 함수를 참조하는 식별자의 선언은 단순히 객체 또는 함수의 선언으로 간단히 참조됩니다.

다른 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.cmain.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 */

두 번째 선언은 a1b1 이라는 이름의 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



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow