수색…


소개

포인터는 다른 오브젝트 또는 함수의 주소를 저장할 수있는 변수 유형입니다.

통사론

  • <데이터 유형> * <변수 이름>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [length]; int * ptrToFirstElem = arr; / * <C99 'length'는 컴파일 시간 상수 여야하며,> = C11의 경우에는 하나 여야합니다. * /
  • int * arrayOfPtrsToInt [length]; / * <C99 'length'는 컴파일 시간 상수 여야하며,> = C11의 경우에는 하나 여야합니다. * /

비고

별표의 위치는 정의의 의미에 영향을 미치지 않습니다.

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

그러나 한 번에 여러 포인터를 정의 할 때는 각 별표가 필요합니다.

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

배열 변수의 이름 앞에 별표가있는 포인터 배열도 가능합니다.

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

일반적인 오류

포인터의 부적절한 사용은 보안 버그 또는 프로그램 충돌을 포함 할 수있는 버그의 원인이되는 경우가 많으며, 대부분 세그먼트 화 오류로 인해 발생합니다.

할당 실패를 확인하지 않음

메모리 할당은 성공할 것이라고 보장되지 않으며 대신 NULL 포인터를 반환 할 수 있습니다. 반환 된 값을 사용하여 할당이 성공했는지 여부를 확인하지 않고 정의되지 않은 동작을 호출합니다. 이것은 보통 충돌로 이어지지만 충돌이 일어날 것이라는 보장이 없기 때문에 문제가 발생할 수도 있습니다.

예를 들어, 안전하지 않은 방법 :

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

안전한 길:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

메모리를 요청할 때 sizeof 대신 리터럴 번호 사용

주어진 컴파일러 / 기계 구성의 경우 유형은 알려진 크기를가집니다. 그러나 주어진 유형 ( char 이 아닌)의 크기가 모든 컴파일러 / 시스템 구성에서 동일하게 정의된다는 표준은 없습니다. 코드가 메모리 할당에 sizeof(int) 대신 4를 사용하면 원래 컴퓨터에서 작동하지만 코드가 다른 컴퓨터 나 컴파일러로 이식 될 필요는 없습니다. 유형의 고정 크기는 sizeof(that_type) 또는 sizeof(*var_ptr_to_that_type) 로 대체해야합니다.

비 휴대용 할당 :

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

휴대용 할당 :

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

또는 더 나은 여전히 ​​:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

메모리 누출

free 메모리를 사용하여 메모리 할당을 해제하지 못하면 프로그램에서 더 이상 사용하지 않는 재사용 할 free 메모리가 생성됩니다. 이를 메모리 누수 라고합니다. 메모리 누출은 메모리 리소스를 낭비하고 할당 실패로 이어질 수 있습니다.

논리 오류

모든 할당은 동일한 패턴을 따라야합니다.

  1. malloc (또는 calloc )을 사용한 할당
  2. 데이터 저장 방법
  3. free 할당 취소

free ( dangling 포인터 ) 호출 후 또는 malloc ( wild pointer ) 호출 이전에 메모리를 사용하거나, free 두 번 호출 ( "double free")하는 등의이 패턴을 따르지 않으면 세그먼트 화 오류가 발생하고 결과는 프로그램의 충돌입니다.

이러한 오류는 일시적이며 디버깅하기 어려울 수 있습니다. 예를 들어 해제 된 메모리는 대개 OS에 의해 즉시 회수되지 않으므로 매달리는 포인터가 잠시 동안 지속되어 작동하는 것처럼 보일 수 있습니다.

Valgrind 가 작동하는 시스템에서 누출 된 메모리와 원래 할당 된 위치를 식별하는 데 매우 중요한 도구입니다.

스택 변수에 포인터 만들기

포인터를 작성해도 가리키는 변수의 수명이 연장되지는 않습니다. 예 :

int* myFunction() 
{
    int x = 10;
    return &x;
}

여기서 x자동 저장 기간 (일반적으로 스택 할당이라고 함) 을가 집니다. 스택에 할당되기 때문에 myFunction 이 실행되는 동안 만 수명이 유지됩니다. myFunction 이 종료 된 후에 변수 x 가 소멸됩니다. 이 함수는 x 의 주소를 얻고 ( &x 사용하여) 호출자에게 반환하고 호출자는 존재하지 않는 변수에 대한 포인터를 남겨 둡니다. 이 변수에 액세스하려고하면 정의되지 않은 동작이 호출됩니다.

대부분의 컴파일러는 함수가 종료 된 후 실제로 스택 프레임을 지우지 않으므로 반환 된 포인터를 역 참조하면 예상되는 데이터가 제공되는 경우가 많습니다. 그러나 다른 함수가 호출되면 가리키는 메모리를 덮어 쓸 수 있으며 가리키는 데이터가 손상된 것처럼 보입니다.

이 문제를 해결하려면 반환 될 변수에 대한 저장소를 malloc 하고 새로 생성 된 저장소에 대한 포인터를 반환하거나 포인터를 반환하는 대신 유효한 포인터가 함수에 전달되도록해야합니다. 예를 들면 다음과 같습니다.

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

증분 / 감소 및 역 참조

당신이 작성하는 경우 *p++ 가리키는 무엇 증가하는 p , 당신은 잘못입니다.

참조 증가 / 감소는 역 참조 전에 실행됩니다. 따라서이 표현은 포인터 증가합니다 p 자체에 의해 지적 된 것을 반환 p 를 변경하지 않고 증가하기 전에.

당신은 작성해야 (*p)++ 가리키는 무엇 증가하는 p .

이 규칙은 또한 감소하는 게시에 적용 : *p-- 포인터 감소시킵니다 p 자체를 가리키는되어 있지 어떤 p .

포인터 Dereferencing

int a = 1;
int *a_pointer = &a;

a_pointer 를 참조 해제하고 a의 값을 변경하려면 다음 작업을 사용합니다.

*a_pointer = 2;

다음 인쇄 문을 사용하여 확인할 수 있습니다.

printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */

그러나, NULL 또는 다른 방법으로 유효하지 않은 포인터를 역 참조하는 것으로 오해됩니다. 이

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

일반적으로 정의되지 않은 동작 입니다. p1 은 유효하지 않은 주소 인 0xbad 를 가리 0xbad 때문에 역 참조 할 수 없습니다. 거기에 무엇이 있는지 누가 압니까? 운영 체제 메모리 또는 다른 프로그램의 메모리 일 수 있습니다. 이처럼 유일한 타임 코드는 하드 코딩 된 주소에 특정 정보를 저장하는 임베디드 개발입니다. p2NULL )이며 유효하지 않으므로 참조 해제 될 수 없습니다.

구조체에 대한 포인터의 참조 해제

우리가 다음과 같은 구조를 가지고 있다고 가정 해 봅시다 :

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

우리는 struct 키워드를 생략하도록 MY_STRUCT 를 정의 할 수 있으므로 사용할 때마다 struct MY_STRUCT 를 입력 할 필요가 없습니다. 그러나 이것은 선택 사항입니다.

typedef struct MY_STRUCT MY_STRUCT;

그런 다음이 구조체의 인스턴스에 대한 포인터가 있으면

MY_STRUCT *instance;

이 문이 파일 범위에 나타나면 프로그램 시작시 instance 가 null 포인터로 초기화됩니다. 이 명령문이 함수 안에 있으면 값은 정의되지 않습니다. 변수는 역 참조 될 수 있기 전에 유효한 MY_STRUCT 변수 나 동적으로 할당 된 공간을 가리 키도록 초기화되어야합니다. 예 :

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

포인터가 유효하면 두 개의 다른 표기법 중 하나를 사용하여 포인터를 참조 해제하여 멤버에 액세스 할 수 있습니다.

int a = (*instance).my_int;
float b = instance->my_float;

이러한 방법 모두 작업하는 동안,이 화살표를 사용하는 것이 더 연습이 -> 오히려 괄호의 조합, 역 참조에 비해 연산자 * 연산자와 점 . 특히 중첩 된 사용법을 사용하면 읽고 이해하기가 더 쉽습니다.

또 다른 중요한 차이점은 다음과 같습니다.

MY_STRUCT copy = *instance;
copy.my_int    = 2;

이 경우 copy 에는 instance 내용의 사본이 instance 있습니다. copy my_int 를 변경해도 instance 는 변경되지 않습니다.

MY_STRUCT *ref = instance;
ref->my_int    = 2;

이 경우 refinstance 대한 참조입니다. 참조를 사용하여 my_int 를 변경하면 instance 가 변경됩니다.

구조체에 대한 포인터 대신 구조체에 대한 포인터를 함수의 매개 변수로 사용하는 것이 일반적입니다. 구조체를 함수 매개 변수로 사용하면 구조체가 클 경우 스택이 오버플로 될 수 있습니다. 구조체에 대한 포인터를 사용하면 포인터에 충분한 스택 공간 만 사용되지만 함수가 함수로 전달되는 구조체를 변경하면 부작용이 발생할 수 있습니다.

함수 포인터

포인터는 함수를 가리키는 데 사용될 수도 있습니다.

기본적인 기능을 살펴 보겠습니다.

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

이제 함수의 타입에 대한 포인터를 정의 해 보겠습니다.

int (*my_pointer)(int, int);

템플릿을 만들려면 다음 템플릿을 사용하십시오.

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

그런 다음이 포인터를 함수에 할당해야합니다.

my_pointer = &my_function;

이제이 포인터를 사용하여 함수를 호출 할 수 있습니다.

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%d\n", result);
}

이 구문이 기본 유형과 더 자연스럽고 일관성이있는 것처럼 보이지만, 함수 포인터에 대한 속성 지정 및 역 참조는 &* 연산자의 사용을 필요로하지 않습니다. 따라서 다음 스 니펫도 똑같이 유효합니다.

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

함수 포인터의 가독성을 높이려면 typedef를 사용할 수 있습니다.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

또 다른 가독성 트릭은 C 표준이 위와 같은 인수 (변수 선언이 아니라)에서 함수 프로토 타입과 비슷한 것으로 함수 포인터를 단순화 할 수 있도록 허용한다는 것입니다. 따라서 함수 정의와 선언에 대해 다음을 동등하게 사용할 수 있습니다.

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

또한보십시오

함수 포인터

포인터 초기화하기

포인터 초기화는 와일드 포인터를 피하는 좋은 방법입니다. 초기화는 간단하며 변수의 초기화와 다르지 않습니다.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

대부분의 운영 체제에서 부주의하게 NULL 로 초기화 된 포인터를 사용하면 프로그램이 즉시 중단되어 문제의 원인을 쉽게 식별 할 수 있습니다. 초기화되지 않은 포인터를 사용하면 종종 진단하기 어려운 버그가 발생할 수 있습니다.

주의:

NULL 포인터를 역 참조하는 결과는 정의되지 않았으므로 프로그램이 실행중인 운영 체제의 자연스러운 동작이라 할지라도 반드시 충돌을 일으키는 것은 아닙니다 . 컴파일러 최적화는 충돌을 숨기고 소스 포인터에서 역 참조가 발생한 지점 전후에 충돌을 발생 시키거나 NULL 포인터 역 참조를 포함하는 코드의 일부가 프로그램에서 예기치 않게 제거되도록합니다. 디버그 빌드는 일반적으로 이러한 동작을 나타내지는 않지만 언어 표준에서는 보장되지 않습니다. 다른 예기치 않은 동작이나 바람직하지 않은 동작도 허용됩니다.

NULL 은 변수, 할당 된 메모리 또는 함수를 가리 NULL 않으므로 가드 값으로 사용하는 것이 안전합니다.

주의:

일반적으로 NULL(void *)0 으로 정의됩니다. 그러나 이것이 할당 된 메모리 주소가 0x0 임을 의미하지는 않습니다. 더 자세한 설명은 NULL 포인터에 대한 C-faq를 참조하십시오.

NULL이 아닌 값을 포함하도록 포인터를 초기화 할 수도 있습니다.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

운영자 주소 (&)

어떤 객체 (즉, 변수, 배열, 구조체, 구조체, 포인터 또는 함수)의 경우 단항 주소 연산자를 사용하여 해당 객체의 주소에 액세스 할 수 있습니다.

한다고 가정

int i = 1;              
int *p = NULL;

그렇다면 p = &i; 변수 i 의 주소를 포인터 p 복사합니다.

그것은 i p 점으로 표현됩니다.

printf("%d\n", *p); i 의 값인 1을 인쇄합니다.

포인터 산술

여기를 참조하십시오 : 포인터 계산

void * 포인터를 인수로 사용하고 표준 함수에 값을 반환합니다.

케이 앤 리

void* 는 객체 유형에 대한 포인터의 모든 유형입니다. 이것을 사용하는 예는 malloc 함수와 같습니다.이 함수는 다음과 같이 선언됩니다.

void* malloc(size_t);

void-to-void 반환 유형은 malloc 의 반환 값을 다른 유형의 객체에 대한 포인터에 할당 할 수 있음을 의미합니다.

int* vector = malloc(10 * sizeof *vector);

일반적으로 void 포인터에 값을 명시 적으로 캐스트 하지 않는 것이 좋습니다. malloc() 경우에 명시 적 형변환을 사용하면 컴파일러는 stdlib.h 를 포함하는 것을 잊어 버린 경우 malloc() 대한 잘못된 반환 유형을 가정하지만 경고하지 않을 수 있습니다. DRY (자신을 반복하지 말 것) 원칙에 더 잘 부합하기 위해 void 포인터의 올바른 동작을 사용하는 경우이기도합니다. 다음 코드는 오타가 문제를 일으킬 수있는 불필요한 추가 장소를 몇 개 포함하고 있습니다 :

int* vector = (int*)malloc(10 * sizeof int*);

유사하게,

void* memcpy(void *restrict target, void const *restrict source, size_t size);

형식에 관계없이 어떤 개체의 주소도 전달 될 수 있기 때문에 void * 로 지정된 인수를 갖습니다. 여기에서도 호출은 캐스트를 사용해서는 안됩니다.

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Const 포인터

단일 포인터

  • int 에의 포인터

    포인터는 다른 정수를 가리킬 수 있으며 int 는 포인터를 통해 변경할 수 있습니다. 이 코드 샘플은 int b b를 가리 키도록 b를 할당 한 다음 b 의 값을 100 합니다.

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • const int 포인터

    포인터는 다른 정수를 가리킬 수 있지만 int 값은 포인터를 통해 변경할 수 없습니다.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • int const 포인터

    포인터는 하나의 int 를 가리킬 수 있지만 int 값은 포인터를 통해 변경할 수 있습니다.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const const int const 포인터

    포인터는 하나의 int 만을 가리킬 수 있고 int 는 포인터를 통해 변경할 수 없습니다.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

포인터 포인터

  • 에 대한 포인터의 포인터 int

    이 코드는 p1 의 주소를 이중 포인터 p ( int* p1 ( intint )를 int )에 할당합니다.

    그런 다음 p1int a 를 가리 키도록 변경 int a . 그런 다음 a의 값을 100으로 변경합니다.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • const int 포인터를 가리키는 포인터

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • 포인터 const 포인터 int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • int 대한 포인터에 대한 const 포인터

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • const int 대한 const 포인터 포인터

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const 포인터로 포인터 const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • int const 포인터에 대한 const 포인터

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

같은 별표, 다른 의미

전제

C 및 C ++에서 포인터 구문을 둘러싼 가장 혼란스러운 점은 포인터 기호 인 별표 ( * )를 변수와 함께 사용할 때 적용되는 두 가지 의미가 실제로 있다는 것입니다.

먼저 * 를 사용하여 포인터 변수를 선언 합니다.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

선언 (또는 곱하기)하지 않을 때 * 는 포인터 변수의 참조해제 하는 데 사용됩니다.

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

기존 포인터 변수에 다른 변수의 주소를 저장하려면 * 사용 하지 말고 다음과 같이하십시오.

p = &another_variable;

동시에 포인터 변수를 선언하고 초기화 할 때 C 프로그래밍 초보자 사이에 흔히 혼동이 생깁니다.

int *p = &i;

int i = 5; 부터 int i = 5;int i; i = 5; 같은 결과를 내고, 그들 중 일부는 int *p = &i;int *p; *p = &i; 똑같은 결과를 가져라. 사실은 아니요, int *p; *p = &i; UB를 초래할 초기화되지 않은 포인터를 경신하려고합니다. 포인터를 선언하거나 참조 해제하지 않을 때는 * 사용하지 마십시오.

결론

별표 ( * )는 포인터가 사용 된 위치에 따라 C에서 두 개의 뚜렷한 의미를가집니다. 변수 선언 내에서 사용되는 경우, 등변면의 오른쪽에있는 은 메모리에있는 주소 에 대한 포인터 값 이어야합니다. 이미 선언 된 변수 와 함께 사용하면 별표는 포인터 값을 역 참조 하고, 지시 된 값을 메모리에두고, 거기에 저장된 값을 할당하거나 검색 할 수있게합니다.

테이크 아웃

포인터를 다룰 때 말하자면 P와 Q를 염두에 두는 것이 중요합니다. 별표를 사용할 때주의 깊게 사용하고, 별표를 사용할 때는 그 의미를 기억하십시오. 이 작은 세부 사항을 간과하면 버그와 정의되지 않은 동작이 발생할 수 있습니다.

포인터 포인터

C에서 포인터는 다른 포인터를 참조 할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

그러나 참조 및 참조는 직접 허용되지 않습니다.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

소개

포인터는 포인터가 변수임을 나타 내기 위해 변수의 유형과 이름 사이에 별표 ( * )가 붙는 것을 제외하고는 다른 변수와 마찬가지로 선언됩니다.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

동일한 선언의 동일한 유형의 포인터 변수 두 개를 선언하려면 각 식별자 앞에 별표 기호를 사용하십시오. 예를 들어,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

Z 퍼 w 드 ( & )로 표시되는 주소 또는 | 조 연산자는 적절한 유형의 포인터에 놓일 수있는 주어진 변수의 주소를 제공합니다.

int value = 1;
pointer = &value;

별표 ( * )로 표시된 간접 참조 또는 참조 취소 연산자는 포인터가 가리키는 객체의 내용을 가져옵니다.

printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */

포인터가 구조체 또는 공용체 유형을 가리키는 경우,이를 참조 해제하고 -> 연산자를 사용하여 멤버에 직접 액세스 할 수 있습니다.

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

C에서 포인터는 재 할당 할 수있는 고유 한 값 유형이며 그렇지 않은 경우에는 고유 한 권한으로 변수로 처리됩니다. 예를 들어 다음 예제는 포인터 (변수) 자체의 값을 인쇄합니다.

printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

포인터는 변경 가능한 변수이기 때문에 유효한 객체를 가리 키지 않을 수도 있고 null로 설정 될 수도 있습니다

pointer = 0;     /* or alternatively */
pointer = NULL;

또는 단순히 유효한 주소가 아닌 임의의 비트 패턴을 포함함으로써 가능합니다. 후자는 포인터가 참조 해제되기 전에 테스트 할 수 없기 때문에 매우 나쁜 상황입니다. 포인터가 null 인 경우에 대해서만 테스트가 있습니다.

if (!pointer) exit(EXIT_FAILURE);

포인터는 유효한 객체를 가리키는 경우에만 참조 해제 될 수 있습니다. 그렇지 않으면 비헤이비어가 정의되지 않습니다. 많은 최신 구현은 세분화 오류 와 같은 오류를 발생시키고 실행을 종료하여 도움을 줄 수 있지만 다른 프로그램은 잘못된 상태로 프로그램을 남겨 둘 수 있습니다.

역 참조 연산자에 의해 반환 된 값은 원래 변수에 대한 변경 가능한 별칭이므로 원래 변수를 수정하여 변경할 수 있습니다.

*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */

포인터도 다시 할당 할 수 있습니다. 즉, 객체를 가리키는 포인터를 나중에 같은 유형의 다른 객체를 가리키는 데 사용할 수 있습니다.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */

다른 변수와 마찬가지로 포인터에는 특정 유형이 있습니다. 예를 들어, long int 에 대한 포인터에 short int 의 주소를 할당 할 수 없습니다. 이러한 동작은 유형 판별이라고하며 C에서는 금지되어 있지만 몇 가지 예외가 있습니다.

포인터는 특정 형식이어야하지만 각 포인터 유형에 할당 된 메모리는 지정된 유형의 크기가 아니라 주소를 저장하기 위해 환경에서 사용하는 메모리와 같습니다.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zu\n", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zu\n", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zu\n", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zu\n", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zu\n", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zu\n", sizeof (short));  /* size 2 bytes */
    return 0;
}

(참고 : C99 또는 C11 표준을 지원하지 않는 Microsoft Visual Studio를 사용하는 경우 위 샘플에서 %zu 대신 %Iu 1 을 사용해야합니다.)

위의 결과는 환경에 따라 다를 수 있지만 모든 환경은 다양한 유형의 포인터에 대해 동일한 크기를 나타냅니다.

카디프 대학교 C 에서 정보를 기반으로 추출

포인터 및 배열

포인터와 배열은 C에서 밀접하게 연결됩니다. C의 배열은 항상 메모리의 연속 된 위치에 보관됩니다. 포인터 연산은 항상 가리키는 항목의 크기에 따라 조정됩니다. 그래서 만약 우리가 3 개의 double과 base에 대한 포인터를 가지고 있다면, *ptr 은 첫 번째 double을 가리키고, *(ptr + 1) 은 두번째, *(ptr + 2) 는 세번째 double을 가리 킵니다. 더 편리한 표기법은 배열 표기법 [] 을 사용하는 것입니다.

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);

따라서 본질적으로 ptr과 배열 이름은 서로 바꿔 쓸 수 있습니다. 이 규칙은 또한 서브 루틴으로 전달 될 때 배열이 포인터로 사라지는 것을 의미합니다.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %s\n", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

포인터는 배열의 모든 요소 나 마지막 요소를 넘어서는 요소를 가리킬 수 있습니다. 그러나 배열 앞에있는 요소를 포함하여 다른 값에 대한 포인터를 설정하는 것은 오류입니다. (이유는 세그먼트 화 된 구조에서 첫 번째 요소 앞에 오는 주소가 세그먼트 경계를 넘을 수 있기 때문에 컴파일러는 마지막 요소 + 1에 대해 발생하지 않도록 보장합니다).


각주 1 : Microsoft 형식 정보는 printf()형식 스펙 구문을 통해 찾을 수 있습니다.

void 포인터를 사용한 다형성 동작

qsort() 표준 라이브러리 함수는 void 포인터를 사용하여 하나의 함수가 매우 다양한 다양한 유형에서 작동하도록하는 좋은 예입니다.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

정렬 할 배열은 void 포인터로 전달되므로 모든 유형의 요소 배열을 조작 할 수 있습니다. 다음 두 인수는 qsort() 에 배열에서 예상되는 요소의 수와 각 요소의 크기 (바이트)를 나타냅니다.

마지막 인수는 두 개의 void 포인터를 취하는 비교 함수에 대한 함수 포인터입니다. 호출자가이 함수를 제공하게함으로써 qsort() 는 모든 유형의 요소를 효과적으로 정렬 할 수 있습니다.

다음은 플로트를 비교하는 비교 함수의 예입니다. qsort() 전달 된 비교 함수에는이 유형 시그니처가 있어야합니다. 그것이 다형성 (polymorphic)으로 만들어지는 방식은 void 포인터 인자를 우리가 비교하고자하는 요소 유형의 포인터로 형변환하는 것입니다.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

우리는 qsort가이 함수를 사용하여 float을 비교한다는 것을 알고 있으므로, 역 참조하기 전에 void 포인터 인수를 다시 float 포인터로 캐스트합니다.

이제 "len"길이의 배열 "배열"에 대한 다형 함수 qsort의 사용법은 매우 간단합니다.

qsort(array, len, sizeof(array[0]), compare_floats);


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