수색…
소개
포인터는 메모리의 위치를 참조하는 주소입니다. 일반적으로 함수 나 데이터 구조가 참조 된 메모리를 복사하지 않고도 메모리를 인식하고 수정할 수 있도록하기 위해 사용됩니다. 포인터는 기본 (내장) 또는 사용자 정의 유형 모두에서 사용할 수 있습니다.
포인터는 "역 참조" *
, "주소" &
"및"화살표 " ->
연산자를 사용합니다. '*'및 '->'연산자는 가리키는 메모리에 액세스하는 데 사용되고 &
연산자는 메모리에서 주소를 가져 오는 데 사용됩니다.
통사론
- <데이터 유형> * <변수 이름>;
- <데이터 유형> * <변수 이름> = & <동일한 데이터 유형의 변수 이름>;
- <데이터 유형> * <변수 이름> = <동일한 데이터 유형의 값>;
- int * foo; // 정수 값을 가리키는 포인터
- int * bar = & myIntVar;
- 긴 막대 [2];
- 긴 * bar [] = {& myLongVar1, & myLongVar2}; // 같음 : long * bar [2]
비고
같은 줄에 여러 포인터를 선언 할 때의 문제점을 알고 있어야합니다.
int* a, b, c; //Only a is a pointer, the others are regular ints.
int* a, *b, *c; //These are three pointers!
int *foo[2]; //Both *foo[0] and *foo[1] are pointers.
포인터 기본 사항
참고 : 다음 모든 경우에 C ++ 11 상수 nullptr
이 있다고 가정합니다. 이전 버전의 경우 nullptr
을 비슷한 역할을하는 데 사용 된 상수 인 NULL
nullptr
.
포인터 변수 만들기
포인터 변수는 특정 *
구문을 사용하여 만들 수 있습니다 (예 : int *pointer_to_int;
.
변수가 포인터 타입 ( int *
) 일 때 변수는 단지 메모리 주소를 포함합니다. 메모리 주소는 기본 유형 ( int
)의 데이터가 저장되는 위치입니다.
차이점은 변수의 크기를 동일한 유형의 포인터 크기와 비교할 때 명확합니다.
// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
long long int foo1;
long long int foo2;
long long int foo3;
} big_struct;
// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;
// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;
/* Produces:
sizeof(bar) = 24
sizeof(p_bar0) = 8
*/
다른 변수의 주소 가져 오기
포인터는 일반적인 변수처럼 서로간에 할당 될 수 있습니다. 이 경우 포인터가 가리키는 실제 데이터 가 아니라 한 포인터에서 다른 포인터로 복사되는 메모리 주소 입니다.
게다가 그들은 null 메모리 위치를 나타내는 nullptr
값을 취할 수 있습니다. nullptr
과 같은 포인터는 잘못된 메모리 위치를 포함하고 있으므로 유효한 데이터를 참조하지 않습니다.
&
변수의 주소 앞에 연산자 &
를 붙임으로써 주어진 타입의 변수의 메모리 주소를 얻을 수 있습니다. &
로 반환되는 값은 변수의 메모리 주소 ( 변수가 범위를 벗어나지 않는 유효한 데이터)를 포함하는 기본 유형에 대한 포인터입니다.
// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;
// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;
// p_bar1 is now nullptr, p_bar2 is &bar.
p_bar0 = p_bar2;
// p_bar0 is now &bar.
p_bar2 = nullptr;
// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr
참고 문헌과는 대조적으로 :
- 두 포인터를 할당해도 할당 포인터가 참조하는 메모리를 덮어 쓰지 않습니다.
- 포인터는 null 일 수 있습니다.
- 운영자의 주소는 명시 적으로 요구됩니다.
포인터의 내용에 액세스하기
주소를 취하는 것은 &
요구하므로 컨텐트에 액세스하는 경우 접두사로 비 참조 연산자 *
사용해야합니다. 포인터가 참조 해제되면 기본 유형 (실제로는 참조)의 변수가됩니다. 그런 다음 const
가 아닌 경우 읽기 및 수정할 수 있습니다.
(*p_bar0).foo1 = 5;
// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;
// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;
// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.
// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;
*
와 연산자의 조합 .
->
로 축약됩니다.
std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " << p_bar0->foo1 << std::endl; // Prints 5
잘못된 포인터를 역 참조
포인터를 역 참조 할 때 유효한 데이터를 가리키는 지 확인해야합니다. 잘못된 포인터 (또는 널 포인터)를 참조 해제하면 메모리 액세스 위반이 발생하거나 가비지 데이터를 읽거나 쓸 수 있습니다.
big_struct *never_do_this() {
// This is a local variable. Outside `never_do_this` it doesn't exist.
big_struct retval;
retval.foo1 = 11;
// This returns the address of `retval`.
return &retval;
// `retval` is destroyed and any code using the value returned
// by `never_do_this` has a pointer to a memory location that
// contains garbage data (or is inaccessible).
}
이러한 시나리오에서 g++
및 clang++
은 경고를 올바르게 발행합니다.
(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc) warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]
따라서 포인터가 함수의 인수 일 때주의해야하며 null 일 수 있습니다.
void naive_code(big_struct *ptr_big_struct) {
// ... some code which doesn't check if `ptr_big_struct` is valid.
ptr_big_struct->foo1 = 12;
}
// Segmentation fault.
naive_code(nullptr);
포인터 작업
포인터에 대해 두 가지 연산자가 있습니다. 주소 연산자 (&) : 피연산자의 메모리 주소를 반환합니다. 내용 중 (Dereference) 연산자 (*) : 연산자에 의해 지정된 주소에있는 변수의 값을 반환합니다.
int var = 20;
int *ptr;
ptr = &var;
cout << var << endl;
//Outputs 20 (The value of var)
cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)
cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr
별표 (*)는 포인터임을 나타내는 간단한 목적으로 포인터를 선언하는 데 사용됩니다. 이것을 지정된 주소에있는 값을 얻는 데 사용되는 역 참조 연산자와 혼동하지 마십시오. 그것들은 단순히 같은 부호로 표현 된 두 가지 다른 것들입니다.
포인터 산술
증가 / 감소
포인터는 증가 또는 감소 될 수 있습니다 (접두어 및 접미어). 포인터를 증가 시키면 포인터 값이 배열에서 현재 가리키는 요소보다 한 요소 앞으로 이동합니다. 포인터를 줄이면 포인터가 배열의 이전 요소로 이동합니다.
포인터가 가리키는 유형이 완료되지 않은 경우 포인터 계산이 허용되지 않습니다. void
는 항상 불완전한 형태입니다.
char* str = new char[10]; // str = 0x010
++str; // str = 0x011 in this case sizeof(char) = 1 byte
int* arr = new int[10]; // arr = 0x00100
++arr; // arr = 0x00104 if sizeof(int) = 4 bytes
void* ptr = (void*)new char[10];
++ptr; // void is incomplete.
끝 요소에 대한 포인터가 증가하면 포인터는 배열 끝을지나 한 요소를 가리 킵니다. 이러한 포인터는 역 참조 될 수 없지만 감소 될 수 있습니다.
배열에서 one-past-the-end 요소에 대한 포인터를 증가 시키거나 배열에서 첫 번째 요소에 대한 포인터를 감소 시키면 정의되지 않은 동작이 발생합니다.
배열이 아닌 객체에 대한 포인터는 크기 1의 배열 인 것처럼 포인터 산술을 위해 처리 될 수 있습니다.
더하기 / 빼기
포인터에 정수 값을 추가 할 수 있습니다. 그들은 1만큼 증가하지 않고 특정 수로 작동합니다. 포인터에서 정수 값을 뺄 수 있고 포인터 감소로 작용할 수 있습니다. 증가 / 감소와 마찬가지로 포인터는 완전한 유형을 가리켜 야합니다.
char* str = new char[10]; // str = 0x010
str += 2; // str = 0x010 + 2 * sizeof(char) = 0x012
int* arr = new int[10]; // arr = 0x100
arr += 2; // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.
포인터 차이점
동일한 유형에 대한 두 포인터 간의 차이를 계산할 수 있습니다. 두 포인터는 같은 배열 객체 내에 있어야합니다. 그렇지 않으면 정의되지 않은 동작이 발생합니다.
동일한 배열에서 두 개의 포인터 P
와 Q
가 주어지면 P
가 배열의 i
번째 요소이고 Q
가 j
번째 요소 인 경우 P - Q
는 i - j
됩니다. 결과의 유형은 <cstddef>
std::ptrdiff_t
입니다.
char* start = new char[10]; // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.