포인터 "역 참조"는 무엇을 의미합니까?


540

설명과 함께 예제를 포함하십시오.


이것은 당신을 도울 수 있습니다 : stackoverflow.com/questions/2795575/…
Harry Joy


24
int *p;정수에 대한 포인터를 정의하고 해당 포인터를 *p역 참조하므로 실제로 p가 가리키는 데이터를 검색합니다.
Peyman 2019

4
Binky 's Pointer Fun ( cslibrary.stanford.edu/104 )은 내용을 명확히하는 포인터에 대한 훌륭한 비디오입니다. @ Erik- 당신은 Stanford CS Library 링크를 설치하게 된 것을 좋아합니다. 거기에 너무 많은 케이크가 있습니다 ...
templatetypedef

6
해리의 대답은 여기서 도움이되는 것과 반대입니다.
짐 발터

답변:


731

기본 용어 검토

그것은이다 일반적으로 충분한 - 당신이 어셈블리를 프로그래밍하지 않는 한 - 직시 포인터 1 2, 제 3 네 번째 등, 프로세스의 메모리에 두 번째 바이트를 참조하여, 숫자 메모리 주소를 포함를 ....

  • 0과 첫 번째 바이트는 어떻게 되었습니까? 글쎄, 우리는 나중에 그것에 도달 할 것이다-아래의 null 포인터를 참조하십시오 .
  • 포인터가 저장하는 내용과 메모리와 주소의 관계에 대한보다 정확한 정의 는이 답변의 끝 부분에있는 "메모리 주소에 대한 추가 정보 및 왜 알 필요가 없는지" 를 참조하십시오.

포인터가 가리키는 메모리의 데이터 / 값에 액세스하려는 경우 (숫자 인덱스가있는 주소의 내용) 포인터 를 역 참조 합니다.

컴퓨터 나 언어마다 다른 표기법을 사용하여 컴파일러 나 인터프리터에게 현재 지적한 객체의 (현재) 값에 관심이 있다고 말하고 있습니다. 아래에서는 C와 C ++에 중점을 둡니다.

포인터 시나리오

p아래와 같은 포인터가 주어지면 C에서 고려하십시오 ...

const char* p = "abc";

... 문자 'a', 'b', 'c'를 인코딩하는 데 사용되는 숫자 값과 텍스트 데이터의 끝을 나타내는 0 바이트가있는 4 바이트는 메모리의 어딘가에 저장되며 그 주소는 데이터는에 저장됩니다 p. 이런 방식으로 C가 메모리에서 텍스트를 인코딩하는 것을 ASCIIZ라고 합니다.

예를 들어, 문자열 리터럴이 주소 0x1000에 있고 p32 비트 포인터가 0x2000에 있으면 메모리 내용은 다음과 같습니다.

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

주소 0x1000에 대한 변수 이름 / 식별자는 없지만 주소를 저장하는 포인터를 사용하여 문자열 리터럴을 간접적으로 참조 할 수 있습니다 p.

포인터 역 참조

문자가 p가리키는 것을 가리 키기 위해 p다음 표기법 중 하나를 사용하여 역 참조합니다 (다시 C).

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

또한 지시 된 데이터를 통해 포인터를 이동하여 이동할 때 참조를 해제 할 수 있습니다.

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

쓸 수있는 데이터가있는 경우 다음과 같은 작업을 수행 할 수 있습니다.

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

위의 컴파일 타임에이라는 변수가 필요하다는 것을 알고 있어야 x하며 코드는 컴파일러가 저장 위치를 ​​정렬하도록 요청하여 주소를 통해 사용할 수 있도록합니다 &x.

구조 데이터 멤버 역 참조 및 액세스

C에서 데이터 멤버가있는 구조에 대한 포인터 인 변수가있는 경우 ->역 참조 연산자를 사용하여 해당 멤버에 액세스 할 수 있습니다 .

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

멀티 바이트 데이터 유형

포인터를 사용하려면 컴퓨터 프로그램에서 가리키는 데이터 유형에 대한 통찰력이 필요합니다. 해당 데이터 유형을 나타내는 데 둘 이상의 바이트가 필요한 경우 포인터는 일반적으로 데이터에서 가장 낮은 번호의 바이트를 가리 킵니다.

따라서 좀 더 복잡한 예를 살펴보십시오.

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

동적으로 할당 된 메모리를 가리키는 포인터

때로는 프로그램이 실행될 때까지 얼마나 많은 메모리가 필요한지 알지 못하고 어떤 데이터가 발생하는지 알 수 있습니다. 그런 다음를 사용하여 동적으로 메모리를 할당 할 수 있습니다 malloc. 주소를 포인터에 저장하는 것이 일반적입니다 ...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

C ++에서 메모리 할당은 일반적으로 new연산자로 수행되며 다음 과 같이 할당 해제됩니다 delete.

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

아래의 C ++ 스마트 포인터 도 참조하십시오 .

주소 손실 및 유출

종종 포인터는 메모리에서 일부 데이터 또는 버퍼가 존재하는 곳의 유일한 표시 일 수 있습니다. 해당 데이터 / 버퍼의 지속적인 사용이 필요하거나 메모리 를 호출 free()하거나 delete누출을 피하는 기능이 필요한 경우 프로그래머는 포인터 사본에서 작동해야합니다 ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... 또는 모든 변경 사항의 역전을 신중하게 조정하십시오 ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

C ++ 스마트 포인터

C ++에서는 스마트 포인터 객체를 사용하여 포인터를 저장 및 관리하고 스마트 포인터의 소멸자가 실행될 때 자동으로 할당을 해제하는 것이 가장 좋습니다 . C ++ 11부터 표준 라이브러리는 unique_ptr할당 된 객체에 대한 단일 소유자가있는 경우 두 가지를 제공 합니다 ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... shared_ptr공유 소유권 ( 참조 횟수 사용 ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

널 포인터

C에서, NULL그리고 0추가로 C ++에서 nullptr, 포인터가 현재 변수의 메모리 주소를 가지고 있지 않다는 것을 나타 내기 위해 사용될 수 있으며, 포인터 산술에서 역 참조되거나 사용되어서는 안됩니다. 예를 들면 다음과 같습니다.

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

C 및 C ++에서 붙박이 숫자 유형은 반드시 디폴트로하지 않는 것처럼 0,도 boolsfalse, 포인터는 항상로 설정되지 않습니다 NULL. 그들이 때 이러한 모든 0 / 허위 / null로 설정되어 static변수 나 (C ++ 전용) 직접 또는 간접 멤버 정적 객체의 변수 또는 염기, 또는 예를 들어 제로 초기화를 (겪게 new T();new T(x, y, z);포인터를 포함하여 T의 회원에 제로 초기화를 수행하는 반면, new T;하지 않습니다).

당신이 할당 할 때 또한 0, NULLnullptr포인터 포인터의 비트는 반드시 모든 리셋되지 않습니다 : 포인터가 하드웨어 수준에서 "0"을 포함, 또는 가상 주소 공간에 주소 0을 참조하지 않을 수 있습니다. 컴파일러는 다른이는 이유가있는 경우 저장하는 것을 허용하지만이하는대로된다 - 당신이 함께 와서 포인터를 비교하면 0, NULL, nullptr또는 그 중 하나를 할당 된 다른 포인터는, 비교해야 작업이 예상대로. 따라서 컴파일러 수준의 소스 코드 아래에서 "NULL"은 C 및 C ++ 언어에서 약간 "마 법적"입니다.

메모리 주소 및 왜 알 필요가 없는지에 대한 추가 정보

보다 엄격하게, 초기화 된 포인터는 하나 NULL또는 ( 가상 가상 ) 메모리 주소를 식별하는 비트 패턴을 저장합니다.

간단한 경우는 이것이 프로세스의 전체 가상 주소 공간에 대한 숫자 오프셋입니다. 보다 복잡한 경우에, 포인터는 특정 메모리 영역에 대해 상대적인 것일 수 있으며, CPU는 CPU "세그먼트"레지스터 또는 비트 패턴으로 인코딩 된 세그먼트 ID의 어떤 방식에 기초하여 선택하거나 및 / 또는 주소를 사용한 기계 코드 지침.

예를 들어, 변수 int*를 가리 키도록 올바르게 초기화 된 int경우 float*- "GPU"메모리의 액세스 메모리로 int캐스트 한 후 변수가 있는 메모리와는 상당히 다른 메모리에 캐스팅 된 후 함수 포인터로 한 번 캐스팅되어 추가로 사용될 수 있습니다. 프로그램을위한 별개의 메모리 홀딩 머신 연산 코드 ( int*이러한 메모리 영역 내 에서 유효하고 임의의 유효하지 않은 포인터 의 숫자 값으로 ).

C 및 C ++와 같은 3GL 프로그래밍 언어는 이러한 복잡성을 숨기는 경향이 있습니다.

  • 컴파일러가 변수 또는 함수에 대한 포인터를 제공하면 변수를 자유롭게 참조 해제 할 수 있으며 (변수가 파괴되거나 할당 해제되지 않는 한) 특정 CPU 세그먼트 레지스터를 미리 복원 해야하는지 여부는 컴파일러의 문제입니다. 사용 된 고유 한 기계 코드 명령

  • 배열의 요소에 대한 포인터를 얻는 경우 포인터 산술을 사용하여 배열의 다른 곳으로 이동하거나 요소의 다른 포인터와 비교할 수있는 배열의 마지막 끝 주소를 구성 할 수 있습니다 배열에서 (또는 포인터 연산에 의해 같은 과거의 끝 값으로 유사하게 이동 한); C와 C ++에서 다시 한 번, "정상 작동"하는지 확인하는 것은 컴파일러의 책임입니다.

  • 공유 메모리 매핑과 같은 특정 OS 기능은 포인터를 제공 할 수 있으며, 주소 범위 내에서 "작동"할 수 있습니다.

  • 유효한 포인터를 이러한 경계를 넘어서 이동하거나 임의의 숫자를 포인터로 캐스트하거나 관련없는 유형으로 캐스트 된 포인터를 사용하려고하면 일반적으로 정의되지 않은 동작 이 있으므로 상위 레벨 라이브러리 및 애플리케이션에서는 피해야하지만 OS, 디바이스 드라이버 등의 코드는 피해야합니다. C 또는 C ++ 표준에 의해 정의되지 않은 동작에 의존해야 할 수도 있습니다. 그럼에도 불구하고 특정 구현이나 하드웨어에 의해 잘 정의되어 있습니다.


p[1] *(p + 1) 동일 ? 즉, 동일한 지침을 생성 p[1] 하고 *(p + 1)생성 합니까 ?
Pacerier

2
@Pacerier : N1570 초안 C 표준의 6.5.2.1/2부터 (온라인에서 처음 발견) "아래 첨자 연산자 []의 정의는 E1 [E2]가 (* ((E1) + (E2))와 동일하다는 것입니다 ). " -컴파일러가 컴파일 초기 단계에서 동일한 표현으로 즉시 변환하지 않는 이유를 상상할 수 없지만 그 후에 동일한 최적화를 적용하지만 아무도 코드가 동일하다는 것을 확실히 증명할 수있는 방법을 알지 못합니다 작성된 모든 컴파일러를 조사하지 않고.
Tony Delroy

3
@Honey : 1000 헥스 값은 단일 바이트 (8 비트) 메모리로 인코딩하기에는 너무 큽니다. 0에서 255 사이의 부호없는 숫자를 1 바이트에 저장할 수만 있습니다. 따라서 주소 2000을 "단지"로 1000 16 진수를 저장할 수 없습니다. 대신 32 비트 시스템은 32 비트 (4 바이트)를 2000에서 2003까지의 주소와 함께 사용합니다. 64 비트 시스템은 64를 사용합니다. 비트-8 바이트-2000에서 2007까지. 어느 쪽이든 기본 주소 p는 2000입니다. 다른 포인터가 p있으면 2000을 4 또는 8 바이트로 저장해야합니다. 희망이 도움이됩니다! 건배.
Tony Delroy

1
@TonyDelroy : 공용체 u에 배열이 포함 된 경우 arrgcc와 clang은 lvalue u.arr[i]가 다른 공용체 멤버와 동일한 스토리지에 액세스 할 수 있음을 인식 하지만 lvalue *(u.arr+i)는 그렇게 할 수 있음을 인식하지 않습니다 . 나는 그 컴파일러의 저자가 후자가 UB를 호출한다고 생각하는지, 전자가 UB를 호출한다고 생각하는지 확실하지 않지만 어쨌든 유용하게 처리해야하지만 두 표현식이 다른 것으로 분명히 볼 수 있습니다.
supercat

3
나는 C / C ++에서 포인터와 그 사용법을 간결하고 간단하게 설명하는 것을 거의 보지 못했습니다.
kayleeFrye_onDeck

102

포인터 역 참조는 포인터가 가리키는 메모리 위치에 저장된 값을 얻는 것을 의미합니다. 연산자 *를 사용하여 역 참조 연산자라고합니다.

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.

15
포인터는 값을 가리 키지 않고 객체를 가리 킵니다 .
Keith Thompson

51
@KeithThompson 포인터는 객체를 가리키는 것이 아니라 객체 (기본적 일 수도 있음)가있는 메모리 주소를 가리 킵니다.
mg30rg

4
@ mg30rg : 당신이 어떤 구별을하고 있는지 잘 모르겠습니다. 포인터 값 주소입니다. 정의에 따라 객체는 "실행 환경에서 데이터 저장 영역으로, 그 내용은 값을 나타낼 수 있습니다"입니다. 그리고 "프리미티브"란 무엇을 의미합니까? C 표준은 그 용어를 사용하지 않습니다.
Keith Thompson

6
@KeithThompson 나는 간신히 지적했다. 당신은 실제로 답에 가치를 더하지 않았고, 용어에 대해서만 nitpicking하고 있었다. 포인터 값은 반드시 주소이며, 이것이 메모리 주소를 가리키는 방법입니다. OOP 주도 세계에서 "개체"라는 단어는 "클래스 인스턴스"로 해석 될 수 있기 때문에 오해의 소지가 있습니다. "copmlex"(구조체 또는 클래스와 같은 데이터 구조)와 반대되는 "기본"
mg30rg

3
이 답변에 배열 첨자 연산자 []가 포인터를 역 참조 a[b]한다는 의미를 추가하겠습니다 *(a + b).
cmaster-복원 monica

20

포인터는 값에 대한 "참조"입니다. 라이브러리 호출 번호와 마찬가지로 책에 대한 참조입니다. 전화 번호를 "참조"하면 실제로 해당 책을 검색하고 검색하는 중입니다.

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

책이 없으면, 사서가 소리를 내기 시작하고 도서관을 닫고, 두 사람이없는 사람이 책을 찾게되는 원인을 조사하도록 설정됩니다.


18

간단히 말하면 역 참조는 해당 포인터가 가리키는 특정 메모리 위치에서 값에 액세스하는 것을 의미합니다.


7

포인터 기본의 코드 및 설명 :

역 참조 작업은 포인터에서 시작하여 화살표를 따라 해당 포인트에 액세스합니다. 목표는 포인트 상태를 보거나 포인트 상태를 변경하는 것입니다. 포인터에 대한 역 참조 작업은 포인터에 포인트가있는 경우에만 작동합니다. 포인트를 할당하고 포인터가 포인트를 가리 키도록 설정해야합니다. 포인터 코드에서 가장 일반적인 오류는 포인트를 설정하는 것을 잊고 있습니다. 코드에서 오류로 인해 가장 일반적인 런타임 충돌은 역 참조 작업 실패입니다. Java에서 런타임 시스템은 잘못된 역 참조를 정중하게 표시합니다. C, C ++ 및 Pascal과 같은 컴파일 된 언어에서 잘못된 역 참조는 때때로 중단되고 때로는 미묘하고 무작위적인 방식으로 메모리를 손상시킵니다.

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}

실제로 x가 가리키는 위치에 메모리를 할당해야합니다. 귀하의 예에는 정의되지 않은 동작이 있습니다.
Peyman 2019

3

역 참조가 실제 가치에 접근한다는 의미이므로 이전의 모든 대답이 잘못되었다고 생각합니다. Wikipedia는 대신 올바른 정의를 제공합니다. https://en.wikipedia.org/wiki/Dereference_operator

포인터 변수에서 작동하며 포인터 주소의 값과 동등한 l 값을 반환합니다. 이것을 포인터의 "역 참조"라고합니다.

즉, 포인터가 가리키는 값에 액세스하지 않고도 포인터를 역 참조 할 수 있습니다. 예를 들면 다음과 같습니다.

char *p = NULL;
*p;

값에 액세스하지 않고 NULL 포인터를 역 참조했습니다. 또는 우리는 할 수 있습니다 :

p1 = &(*p);
sz = sizeof(*p);

다시 참조하지만 값에 액세스하지는 않습니다. 이러한 코드는 충돌하지 않습니다. 실제로 잘못된 포인터로 데이터에 액세스 하면 충돌이 발생합니다 . 그러나 불행히도 표준에 따르면 유효하지 않은 포인터를 참조 해제하는 것은 실제 데이터를 터치하지 않더라도 정의되지 않은 동작입니다 (몇 가지 예외는 있음).

간단히 말해 : 포인터 역 참조는 역 참조 연산자를 적용하는 것을 의미합니다. 이 연산자는 나중에 사용하기 위해 l 값을 반환합니다.


글쎄, 당신은 세그먼트 오류로 이어질 NULL 포인터를 역 참조했다.
arjun gaur

또한 포인터를 가리키는 메모리 위치에서 값을 얻거나 값에 액세스하는 것을 의미하는 '포인터 참조 취소'가 아닌 '역 참조 연산자'를 검색했습니다.
arjun gaur

시도 했습니까? 나는했다. 다음은 충돌하지 않습니다 :`#include <stdlib.h> int main () {char * p = NULL; *피; 리턴 0; }`
stsp

1
@stsp 코드가 충돌하지 않기 때문에 앞으로 또는 다른 시스템에서는 그렇지 않다는 의미는 아닙니다.

1
*p;정의되지 않은 동작을 유발합니다. 당신이 값에 액세스하지 않는 역 참조 바로 것을 있지만 그 자체로 , 코드는 *p; 않습니다 액세스에게 값입니다.
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.