포인터 설명을 가리키는 포인터


142

나는 이것을 따르고 있었다 포인터에 대한 포인터의 작동 방식에 대한 자습서 를 .

관련 구절을 인용하겠습니다.


    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;

이제 우리는 설정할 수 있습니다

    int **ipp = &ip1;

ipp포인트 ip1에있는 점 i. *ipp이다 ip1, 그리고 **ipp이다i 또는 5입니다. 다음과 같이 익숙한 상자 및 화살표 표기법으로 상황을 설명 할 수 있습니다.

여기에 이미지 설명을 입력하십시오

그렇다면 우리는 말합니다

    *ipp = ip2;

우리가 가리키는 포인터 변경했습니다 ipp(즉, ip1의 사본을 포함)을 ip2, 그래서 그것을 ( ip1) 지금 시점에서 j:

여기에 이미지 설명을 입력하십시오


내 질문은 : 왜 두 번째 그림에서 ipp여전히 가리키고 ip1있지만 그렇지 ip2않습니까?


6
ipp정의 할 때 유형을 추가 하십시오. 질문이 완료되었습니다. ;-)
zmo

1
때문에 ipp가리키는 ip1그렇게하는 것은 *ipp = ip2으로 동일합니다ip1 = ip2
Grijesh 차우

1
뾰족한 유형에서 멀리 떨어진 별표 작성을 중단 할 수 있습니까? 특히 형식 선언 외부에서 완전히 다른 것을 의미 할 int **ipp때보다 직관적이지 않습니다 . int** ipp**ipp
Darkhogg

4
@ Darkhogg int **ipp는 나에게 매우 직관적 인 것 같습니다. 그것은 내가 만들고 있다는 것을 의미 **ipp합니다 int. 뭐가 진실이지.
ithisa

2
의 유형 : @ user54609 의미와 떨어져 개인 직관성이 생각 ipp입니다 int**, 그래서 단지 쓰기 int**대신 마법의 " imp역 참조는 int이며" 이해 .
Manu343726

답변:


143

포인팅 비유에 대해 잠시 잊어 버려요. 포인터가 실제로 포함하는 것은 메모리 주소입니다. 는 &연산자 "주소"입니다 - 객체의 메모리 주소를 반환 즉. *운영자는 해당 메모리 주소의 객체를 반환, 포인터, 주소를 포함하는 포인터를 제공하여 예를 의미 당신에게 객체를 제공합니다. 당신이 그래서 *ipp = ip2, 당신이하고있는 것은 *ipp개최 된 주소의 객체를 얻을 수있는 ipp이는 ip1다음에 할당 ip1에 저장된 값 ip2의 주소입니다,j .

간단히
& -> 주소
*-> 값


14
&와 *는 그렇게 쉬운 적이 없다
Ray

7
혼란의 주요 원인은 변수 선언 중에 변수가 실제로 특정 데이터 유형에 대한 포인터임을 나타내는 데 사용되는 * 연산자의 모호성 때문이라고 생각합니다. 그러나 다른 한편으로, 포인터에서 가리키는 변수의 내용에 액세스하기 위해 명령문에서도 사용됩니다 (역 참조 연산자).
Lucas A.

43

의 값이 ipp아닌 에 의해 지정된 값을 변경했기 때문 입니다 ipp. 따라서 ipp여전히 ip1(의 값 ipp) 을 (를 ) 가리키는 ip1값은 이제 값과 동일 ip2하므로 둘 다를 가리 킵니다 j.

이:

*ipp = ip2;

와 같다:

ip1 = ip2;

11
가치 사이의 차이를 지적 할 수 있음 int *ip1 = &i*ipp = ip2;당신이를 제거하는 경우, 즉 int첫 번째 문에서 다음 과제는 매우 비슷하지만,이 *두 경우에서 매우 다른 일을하고있다.
Crowman

22

C 태그의 초보자 질문과 마찬가지로이 질문은 첫 번째 원칙으로 돌아가서 답변 할 수 있습니다.

  • 포인터는 일종의 가치입니다.
  • 변수는 값을 포함합니다.
  • 그만큼 &연산자는 포인터에 가변 변.
  • *조작 변수에 대한 포인터를 온.

(기술적으로는 "variable"대신 "lvalue"라고 말해야하지만 가변 스토리지 위치를 "variables"로 설명하는 것이 더 명확하다고 생각합니다.

그래서 우리는 변수가 있습니다 :

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

변수 는 포인터를 ip1 포함 합니다. &연산자 변 i포인터에 해당 포인터 값이 할당된다 ip1. 그래서 ip1 대한 포인터를 포함i .

변수 는 포인터를 ip2 포함 합니다. &연산자 변 j포인터에 해당 포인터에 할당된다 ip2. 그래서 ip2 포함 에 대한 포인터를 j.

int **ipp = &ip1;

변수 ipp는 포인터를 포함합니다. &연산자 가변 변 ip1포인터에 해당 포인터 값이 할당된다 ipp. 그래서 ipp에 대한 포인터를 포함ip1 .

지금까지 이야기를 요약 해 봅시다.

  • i 5 개 포함
  • j 6을 포함
  • ip1 "포인터를 포함 i"를 포함
  • ip2 "포인터를 포함 j"를 포함
  • ipp"포인터 ip1"를 포함

이제 우리는 말합니다

*ipp = ip2;

*연산자 변수로 포인터를 다시 온. 우리 ipp는 "의 포인터를 가져 와서 ip1변수로 바꾸는 값 "을 가져옵니다 . ip1물론 어떤 변수입니까?

따라서 이것은 단순히 다른 표현 방법입니다

ip1 = ip2;

따라서 값을 가져옵니다 ip2. 무엇입니까? "포인터 j" 해당 포인터 값을에 할당 ip1하므로 ip1이제는 "포인터 j"

한 가지만 변경했습니다 : 값 ip1:

  • i 5 개 포함
  • j 6을 포함
  • ip1"포인터 j"를 포함
  • ip2"포인터 j"를 포함
  • ipp"포인터 ip1"를 포함

ipp아직도 가리키고 ip1있지 ip2않습니까?

변수를 할당하면 변경됩니다. 과제를 세십시오. 할당보다 변수에 더 많은 변화가있을 수 없습니다! 당신은에 할당하여 시작 i, j, ip1, ip2ipp. 그런 다음에 할당하면 *ipp"할당"과 같은 의미 ip1입니다. ipp두 번째로 할당 하지 않았으므로 변경되지 않았습니다!

변경 ipp하고 싶다면 실제로 다음을 할당해야합니다 ipp.

ipp = &ip2;

예를 들어.


21

이 코드가 도움이되기를 바랍니다.

#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;
    int** ipp = &ip1;
    printf("address of value i: %p\n", &i);
    printf("address of value j: %p\n", &j);
    printf("value ip1: %p\n", ip1);
    printf("value ip2: %p\n", ip2);
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
    *ipp = ip2;
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
}

출력 :

여기에 이미지 설명을 입력하십시오


12

저의 개인적인 견해는 화살표가이 방법을 가리 키거나 포인터를 이해하기 어렵게 만드는 그림입니다. 그것들을 추상적이고 신비한 존재처럼 보이게합니다. 그들은 아닙니다.

컴퓨터의 다른 모든 것과 마찬가지로 포인터는 숫자입니다. 입니다. "포인터"라는 이름은 "주소를 포함하는 변수"라는 멋진 표현입니다.

따라서 컴퓨터가 실제로 어떻게 작동하는지 설명함으로써 주변 환경을 자극하겠습니다.

우리는 int이름 i과 값 5를가집니다. 이것은 메모리에 저장됩니다. 메모리에 저장된 모든 것과 마찬가지로 주소가 필요하거나 찾을 수 없습니다. 말은 수 있습니다 i주소 0x12345678에 끝과 그 친구 j그냥 후 6 개 끝을 값. int가 4 바이트이고 포인터가 4 바이트 인 32 비트 CPU를 가정하면 변수는 다음과 같이 실제 메모리에 저장됩니다.

Address     Data           Meaning
0x12345678  00 00 00 05    // The variable i
0x1234567C  00 00 00 06    // The variable j

이제 우리는이 변수들을 지적하려고합니다. int int* ip1, 및 하나에 대한 하나의 포인터를 만듭니다 int* ip2. 컴퓨터의 모든 것과 마찬가지로 이러한 포인터 변수도 메모리 어딘가에 할당됩니다. 이들이 메모리 바로 다음에 인접한 주소에서 끝나는 것으로 가정하자 j. 이전에 할당 된 변수 ip1=&i;의 주소 ( "i의 주소를 ip1로 복사")와를 포함하도록 포인터를 설정했습니다 ip2=&j. 줄 사이에 일어나는 일은 다음과 같습니다.

Address     Data           Meaning
0x12345680  12 34 56 78    // The variable ip1(equal to address of i)
0x12345684  12 34 56 7C    // The variable ip2(equal to address of j)

우리가 얻은 것은 아직 숫자를 포함하는 4 바이트의 메모리 덩어리였습니다. 보이지 않는 곳에 신비로운 마법 화살이 없습니다.

실제로 메모리 덤프를 살펴보면 주소 0x12345680에 int또는int* . 차이점은 프로그램이이 주소에 저장된 내용을 사용하도록 선택하는 방법입니다. (우리 프로그램의 임무는 실제로 CPU 에게이 숫자로 무엇을 해야하는지 알려주는 것입니다.)

그런 다음을 사용하여 또 다른 수준의 간접 성을 추가 int** ipp = &ip1;합니다. 다시, 우리는 단지 메모리 덩어리를 얻습니다.

Address     Data           Meaning
0x12345688  12 34 56 80    // The variable ipp

패턴이 익숙한 것 같습니다. 숫자를 포함하는 4 바이트의 또 다른 청크.

위의 가상의 작은 RAM의 메모리 덤프가 있으면 이러한 포인터가 가리키는 위치를 수동으로 확인할 수 있습니다. ipp변수 의 주소에 저장된 내용을 들여다 보고 내용을 0x12345680으로 찾습니다. 물론 주소 ip1가 저장 되는 주소 입니다. 그 주소로 가서 내용을 확인하고 주소를 찾을 수 있습니다.i 다음 해당 주소로 이동하여 숫자 5를 찾을 수 있습니다.

따라서 ipp의 내용을 취 *ipp하면 포인터 변수의 주소를 얻게됩니다 ip1. *ipp=ip2우리는 ip2를 ip1에 복사하여 쓰는 것과 동일합니다 ip1=ip2. 두 경우 모두

Address     Data           Meaning
0x12345680  12 34 56 7C    // The variable ip1
0x12345684  12 34 56 7C    // The variable ip2

(이 예제는 빅 엔디안 CPU에 제공되었습니다)


5
나는 당신의 요점을 생각하지만, 포인터를 추상적이고 신비한 엔티티로 생각하는 것이 가치가 있습니다. 포인터의 특정 구현 은 숫자에 불과하지만 스케치하는 구현 전략은 구현의 요구 사항 이 아니라 일반적인 전략입니다. 포인터는 int와 크기가 같을 필요가 없으며 포인터는 플랫 가상 메모리 모델의 주소 일 필요는 없습니다. 이것들은 단지 구현 세부 사항입니다.
Eric Lippert

@EricLippert 실제 메모리 주소 나 데이터 블록을 사용하지 않음으로써이 예제를 더 추상적으로 만들 수 있다고 생각합니다. 그것이 같은 것을 나타내는 표라면location, value, variable 위치 1,2,3,4,5와 가치가 어디에 있는지를A,1,B,C,3 , 화살표를 사용하지 않고도 포인터의 해당 아이디어를 쉽게 설명 할 수 있습니다. 어떤 구현을 선택하든 어떤 위치에는 값이 존재하며 이는 화살표로 모델링 할 때 난독 화되는 퍼즐 조각입니다.
MirroredFate

@EricLippert 내 경험에 따르면 포인터를 이해하는 데 문제가있는 대부분의 C 프로그래머는 추상적 인 인공 모델을 먹은 사람들입니다. 추상화는 아니다오늘날 C 언어의 전체 목적이 하드웨어에 가깝다는 점에서 도움 . C를 배우고 있지만 하드웨어에 가까운 코드를 작성하지 않으려는 경우 시간을 낭비하고 있습니다 . 컴퓨터가 작동하는 방식을 알고 싶지 않고 높은 수준의 프로그래밍 만하는 경우 Java 등이 훨씬 좋습니다.
Lundin

@EricLippert 그리고 네, 포인터가 주소와 반드시 일치 할 필요는없는, 포인터가 불분명 한 다양한 구현이 존재할 수 있습니다. 그러나 화살표 그리기는 그 작동 방식을 이해하는 데 도움이되지 않습니다. 어떤 시점에서는 추상적 인 사고를 떠나 하드웨어 수준으로 내려 가야합니다. 그렇지 않으면 C를 사용해서는 안됩니다. 순전히 추상적 인 고수준 프로그래밍을위한 훨씬 더 적합하고 현대적인 언어가 있습니다.
Lundin

@Lundin : 나도 화살표 다이어그램을 좋아하지 않습니다. 데이터 로 화살표의 개념은 까다로운 것입니다. 나는 추상적이지만 화살표가없는 것을 선호합니다. &변수 의 연산자는 해당 변수를 나타내는 동전을 제공합니다. *그 동전 의 연산자는 변수를 돌려줍니다. 화살이 필요 없습니다!
Eric Lippert

8

과제를 확인하십시오.

ipp = &ip1;

결과 ipp를 가리 킵니다 ip1.

정도 ipp점하기 위해 ip2, 우리는 유사한 방식으로 변경해야합니다,

ipp = &ip2;

우리는 분명히하지 않습니다. 대신로 지정된 주소 에서 값을 변경하고 ipp있습니다.
다음을 수행함으로써

*ipp = ip2;

에 저장된 값을 바꾸고 ip1있습니다.

ipp = &ip1, 의미합니다 *ipp = ip1 = &i,
지금,*ipp = ip2 = &j .
따라서 *ipp = ip2본질적으로와 동일합니다 ip1 = ip2.


5
ipp = &ip1;

더 이상 할당이의 값을 변경하지 않았습니다 ipp. 이것이 여전히을 가리키는 이유 ip1입니다.

*ipp, 즉 으로하는 일이을 가리키는 ip1사실을 변경하지는 않습니다 .ippip1


5

내 질문은 : 왜 두 번째 그림에서 ipp는 여전히 ip1을 가리키고 ip2는 가리지 않습니까?

당신은 멋진 사진을 배치, 나는 좋은 아스키 아트를 만들려고합니다 :

@ Robert-S-Barnes가 그의 대답에서 말했다 : 포인터를 잊어 버리고 무엇이 무엇을 가리키는 지 기억하지만 메모리 측면에서 생각하십시오. 기본적으로 int*변수 int**의 주소를 포함하고 변수의 주소를 포함 하는 변수의 주소를 포함한다는 의미입니다 . 그런 다음 포인터의 대수를 사용하여 값 또는 주소에 액세스 할 수 있습니다 : &foomeans address of foo*foomeans value of the address contained in foo.

따라서 포인터가 메모리를 다루는 것에 관한 것이므로 실제로 "유형"으로 만드는 가장 좋은 방법은 포인터 대수가 메모리에 대해 수행하는 작업을 보여주는 것입니다.

따라서 프로그램 메모리는 다음과 같습니다 (예제 목적으로 단순화).

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [   |   |   |   |   ]

초기 코드를 할 때 :

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

메모리는 다음과 같습니다.

name:    i   j ip1 ip2
addr:    0   1   2   3
mem : [  5|  6|  0|  1]

당신이 볼 수 ip1ip2의 주소를 얻을 수 ijipp여전히 존재하지 않습니다. 주소는 단순히 특수 유형으로 저장된 정수라는 것을 잊지 마십시오.

그런 다음 다음 ipp과 같이 선언하고 정의 했습니다.

int **ipp = &ip1;

여기 당신의 기억이 있습니다 :

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  0|  1|  2]

다음, 당신은에 저장된 주소가 가리키는 값을 변경하고 ipp저장 주소입니다 ip1:

*ipp = ip2;

프로그램의 메모리는

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  1|  1|  2]

주의 : 같은 int*특별한 유형이 내가 생각하는 나는, 항상 같은 줄에 여러 개의 포인터를 선언 피하기 위해 선호 int *x;또는 int *x, *y;표기가 오해의 소지가 될 수있다. 나는 쓰기를 선호합니다int* x; int* y;

HTH


귀하의 예를 들어, 초기 값은 아니 ip2어야합니다 . 34
Dipto

1
오, 방금 선언 순서와 일치하도록 메모리를 변경했습니다. 내가 그렇게 고쳤다 고 생각하니?
zmo

5

네가 말할 때

*ipp = ip2

가리키는 객체 ipp의 방향을 가리 키기 위해 '객체가 가리키는 객체'라고 말하고 ip2있습니다.

당신은 ipp지적 하고 있지 않습니다 ip2.


4

역 참조 연산자를 추가하면 * 를 포인터에 포인터에서 지정된 객체로 리디렉션됩니다.

예 :

int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
             //     it's not the dereference operator in this context
*p;          // <-- this expression uses the pointed-to object, that is `i`
p;           // <-- this expression uses the pointer object itself, that is `p`

따라서:

*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
            //     therefore, `ipp` still points to `ip1` afterwards.

3

ipp을 가리 키 려면 을 ( ip2를) 말해야 ipp = &ip2;합니다. 그러나 이것은 ip1여전히을 가리키고 i있습니다.


3

아주 처음부터

ipp = &ip1;

이제 다음과 같이 역 참조하십시오.

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 

3

다음과 같이 표시된 각 변수를 고려하십시오.

type  : (name, adress, value)

변수는 다음과 같이 표현해야합니다

int   : ( i ,  &i , 5 ); ( j ,  &j ,  6); ( k ,  &k , 5 )

int*  : (ip1, &ip1, &i); (ip1, &ip1, &j)

int** : (ipp, &ipp, &ip1)

의 가치 ipp&ip1다음과 같습니다.

*ipp = ip2;

addess &ip1의 값을의 값으로 변경합니다. ip2즉, 다음과 같이 ip1변경됩니다.

(ip1, &ip1, &i) -> (ip1, &ip1, &j)

그러나 ipp여전히 :

(ipp, &ipp, &ip1)

따라서 ippstill 값은 &ip1여전히을 가리 킵니다 ip1.


1

의 포인터를 변경하고 있기 때문입니다 *ipp. 그 뜻은

  1. ipp (가칭) ---- 안으로 들어가십시오.
  2. 내부 ipp는 주소입니다ip1 입니다.
  3. 이제 *ipp(내부 주소)로 이동하십시오 ip1.

지금 우리는 ip1입니다. *ipp(예 :) ip1= ip2. .so의
ip2주소를 포함 하면 콘텐츠가 ip2의 포함 (예 : j의 주소)으로 대체되며 콘텐츠를 변경하지 않습니다 . 그게 다야.jip1ipp


1

*ipp = ip2; 암시 :

ip2로 지정된 변수에 지정하십시오 ipp. 따라서 이것은 다음과 같습니다.

ip1 = ip2;

의 주소를 ip2에 저장하려면 다음을 ipp수행하십시오.

ipp = &ip2;

이제를 ipp가리 킵니다 ip2.


0

ipp포인터 유형 객체에 대한 포인터 값을 가질 수 있습니다 . 당신이 할 때

ipp = &ip2;  

그런 다음은 포인터에 대한 포인터 유형의 ( ) 변수 (포인터)ipp주소를ip2 포함합니다 . 이제 두 번째 그림 의 화살표 가을 가리 킵니다 . &ip2ippip2

위키는 말합니다 : 연산자는 참조 연산자는 포인터 변수를 운영하고있다, 그리고 반환 리터 값을
* 포인터 어드레스의 값과 동등 (변수). 이것을 포인터 역 참조라고합니다.

derefrence에 *연산자를 적용 ipp하여 포인터int 유형 의 l 값 에 적용 합니다. 역 참조 된 l- 값 *ipp은에 대한 포인터intint 유형 이며 유형 데이터 의 주소를 보유 할 수 있습니다 . 진술 후

ipp = &ip1;

ipp의 주소를 보유 ip1하고 *ipp(가리키는)의 주소를 들고있다 i. 당신은 그것이 *ipp별명 이라고 말할 수 있습니다 ip1. 두 **ipp*ip1에 대한 별칭이다 i.
함으로써

 *ipp = ip2;  

*ippip2같은 위치 만에 두 점은 ipp여전히 가리키는 ip1.

무엇 *ipp = ip2;실제로하는 일은 그 사본의 내용이다 ip2(의 주소 j에) ip1(로 *ipp의 별칭입니다 ip1효과가), 두 포인터를 만들고 ip1ip2같은 객체를 가리키는 ( j).
그래서, 두 번째 그림에서 의 화살표 ip1ip2가리키는 j동안 ipp여전히 가리키는 ip1더 수정의 값을 변경할 수행되지 않습니다으로ipp .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.