C Language
포인터
수색…
소개
포인터는 다른 오브젝트 또는 함수의 주소를 저장할 수있는 변수 유형입니다.
통사론
- <데이터 유형> * <변수 이름>;
- 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
메모리가 생성됩니다. 이를 메모리 누수 라고합니다. 메모리 누출은 메모리 리소스를 낭비하고 할당 실패로 이어질 수 있습니다.
논리 오류
모든 할당은 동일한 패턴을 따라야합니다.
-
malloc
(또는calloc
)을 사용한 할당 - 데이터 저장 방법
-
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
때문에 역 참조 할 수 없습니다. 거기에 무엇이 있는지 누가 압니까? 운영 체제 메모리 또는 다른 프로그램의 메모리 일 수 있습니다. 이처럼 유일한 타임 코드는 하드 코딩 된 주소에 특정 정보를 저장하는 임베디드 개발입니다. p2
는 NULL
)이며 유효하지 않으므로 참조 해제 될 수 없습니다.
구조체에 대한 포인터의 참조 해제
우리가 다음과 같은 구조를 가지고 있다고 가정 해 봅시다 :
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;
이 경우 ref
는 instance
대한 참조입니다. 참조를 사용하여 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
(int
를int
)를int
)에 할당합니다.그런 다음
p1
이int 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);