C에서 포인터에 대한 포인터는 어떻게 작동합니까?


171

C에서 포인터에 대한 포인터는 어떻게 작동합니까? 언제 사용하겠습니까?


43
숙제가 아닙니다 .... 그냥 알고 싶었습니다.. coz C 코드를 읽을 때 많이 봅니다.

1
포인터에 대한 포인터는 특별한 경우가 아니므로 void **에 대해 이해하지 못하는 것을 이해하지 못합니다.
— akappa

2D 배열의 가장 좋은 예는 명령 행 args "prog arg1 arg2"가 char ** argv에 저장되어있는 것입니다. 그리고 호출자가 메모리를 할당하지 않으려면 (호출 된 함수가 메모리를 할당합니다)
— resultsway

1
Git 2.0에서 "포인터 투 포인터"사용법의 좋은 예가 있습니다 : 아래 답변을
— VonC

답변:


359

8 비트 주소 (따라서 256 바이트의 메모리)를 가진 8 비트 컴퓨터를 가정 해 봅시다. 이것은 해당 메모리의 일부입니다 (맨 위의 숫자는 주소입니다).

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

여기서 볼 수있는 것은 주소 63에서 "hello"문자열이 시작된다는 것입니다. 이 경우 메모리에서 "hello"가 유일하게 발생하면

const char *c = "hello";

... c(읽기 전용) 문자열 "hello"에 대한 포인터로 정의 되므로 값 63을 포함합니다. c위의 예에서 위치 58에 저장해야합니다 . 물론 문자 만 가리킬 수는 없습니다. 다른 포인터에도. 예 :

const char **cp = &c;

이제를 cp가리키고 c있습니다. 즉, 주소는 c(58)입니다. 더 나아갈 수 있습니다. 치다:

const char ***cpp = &cp;

이제 cpp주소를 저장합니다 cp. 따라서 값은 55 (위의 예를 기반으로 함)이며 값은 60입니다.


왜 포인터를 포인터로 사용 하는지에 관해서 :

  • 배열의 이름은 일반적으로 첫 번째 요소의 주소를 생성합니다. 따라서 배열에 유형의 요소가 포함 된 경우 배열 t에 대한 참조에는 유형이 t *있습니다. 이제 유형의 배열 배열을 고려하십시오 t. 당연히이 2D 배열에 대한 참조는 type (t *)*=을 t **가지므로 포인터에 대한 포인터입니다.
  • 문자열 배열은 1 차원으로 들리지만 문자열은 문자 배열이므로 실제로는 2 차원입니다. 따라서 : char **.
  • 함수는 f형의 인수를 허용해야합니다 t **그것이 유형의 변수를 변경하는 경우 t *.
  • 여기에 나열하기에는 너무 많은 다른 이유가 있습니다.

7
예 좋은 예. 나는 그들이 무엇인지 이해합니다. 그러나 언제 어떻게 사용 하는가가 더 중요합니다. 지금 ..

2
Stephan은 기본적으로 Kernighan & Richie의 The C Programming Language의 다이어그램을 잘 재생했습니다. C를 프로그래밍하고이 책을 가지고 있지 않고 종이 문서가 멋지다면, 나는 그것을 얻는 것이 좋습니다. 예제에서 매우 분명한 경향이 있습니다.
— J. Polfer

4
char * c = "hello"는 const char * c = "hello"여야합니다. 또한 "배열이 첫 번째 요소의 주소로 저장된다"고 말하는 것은 오해의 소지가 있습니다. 배열은 배열로 저장됩니다. 종종 그 이름은 첫 번째 요소에 대한 포인터를 제공하지만 항상 그런 것은 아닙니다. 포인터에 대한 포인터에 대해서는 함수가 매개 변수로 전달 된 포인터를 수정해야 할 때 유용하다고 말합니다 (대신 포인터를 포인터로 전달).
— Bastien Léonard

4
이 답변을 잘못 해석하지 않으면 잘못 보입니다. c는 58에 63으로, cp는 55에 58으로, cpp는 다이어그램에 표시되지 않습니다.
— Thanatos

1
좋아 보인다 사소한 문제보다는 내가 말하는 것을 막는 모든 것이 었습니다. 설명 자체가 훌륭했습니다. 투표로 변경. (아마도 stackoverflow는 포인터를 검토해야합니까?)
— Thanatos

46

C에서 포인터에 대한 포인터는 어떻게 작동합니까?

먼저 포인터는 다른 변수와 마찬가지로 변수이지만 변수의 주소를 보유합니다.

포인터에 대한 포인터는 다른 변수와 마찬가지로 변수이지만 변수의 주소를 보유합니다. 그 변수는 포인터 일뿐입니다.

언제 사용하겠습니까?

힙의 일부 메모리에 대한 포인터를 리턴해야하지만 리턴 값을 사용하지 않는 경우이를 사용할 수 있습니다.

예:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

그리고 당신은 이것을 다음과 같이 부릅니다.

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

모든 C 프로그램의 main () 인수에 argv에 대한 포인터가있는 것처럼 다른 용도도 있습니다. 여기서 각 요소에는 명령 행 옵션 인 문자 배열이 있습니다. 포인터 포인터를 사용하여 2 차원 배열을 가리키는 경우 2 차원 배열에 대한 포인터를 사용하는 것이 좋습니다.

왜 위험한가요?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

다음은 2 차원 배열에 대한 포인터의 예입니다.

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

ROWS 및 COLUMNS에 대해 가변 개수의 요소를 지원하려는 경우 2 차원 배열에 대한 포인터를 사용할 수 없습니다. 그러나 미리 알면 2 차원 배열을 사용하게됩니다.


32

Git 2.0에서 7b1004b를 커밋 하는 포인터 사용법에 대한 포인터의 "실제"코드 예제를 좋아합니다 .

리누스는 한 번 말했다 :

저는 실제로 더 많은 사람들이 핵심 저수준 종류의 코딩을 이해하기를 바랍니다. 잠금없는 이름 조회와 같은 크고 복잡한 것은 아니지만 포인터 대 포인터 등을 잘 사용합니다.
예를 들어, "이전"항목을 추적하여 단일 링크 목록 항목을 삭제하는 사람들이 너무 많습니다. 그런 다음 항목을 삭제하려면 다음과 같이하십시오.

if (prev)
  prev->next = entry->next;
else
  list_head = entry->next;

그런 코드를 볼 때마다 "이 사람은 포인터를 이해하지 못합니다"로 이동합니다. 슬프게도 매우 흔합니다.

포인터를 이해하는 사람들은 " 항목 포인터에 대한 포인터 "를 사용하고 list_head의 주소로 초기화합니다. 그런 다음 목록을 탐색 할 때 조건부를 사용하지 않고 항목을 제거 할 수 있습니다.

*pp =  entry->next

http://i.stack.imgur.com/bpfxT.gif

단순화를 적용하면 2 줄의 주석을 추가하는 동안에도이 기능에서 7 줄을 잃을 수 있습니다.

-   struct combine_diff_path *p, *pprev, *ptmp;
+   struct combine_diff_path *p, **tail = &curr;

크리스는 지적 코멘트에 2016 년 비디오 "로 리누스 토발즈 (Linus Torvalds)의 더블 포인터 문제 로" 필립 Buuck .


쿠마는 지적 코멘트에 "블로그 게시물 의 이해 포인터에 리누스 ," Grisha Trubetskoy는 설명합니다 :

다음과 같이 정의 된 연결 목록이 있다고 가정하십시오.

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;

처음부터 끝까지 반복하고 값이 to_remove 값과 같은 특정 요소를 제거해야합니다.
가장 확실한 방법은 다음과 같습니다.

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) { /* line 4 */
    if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
        if (prev)
           prev->next = entry->next; /* remove the entry ; line 7 */
        else
            head = entry->next;      /* special case - first entry ; line 9 */

    /* move on to the next entry */
    prev = entry;
    entry = entry->next;
}

우리가 위에서하고있는 것은 :

  • entry가가 될 때까지 목록을 반복하여 목록 NULL끝에 도달했음을 의미합니다 (4 행).
  • 항목을 발견하면 제거하려고합니다 (5 행).
    • 현재 다음 포인터의 값을 이전 포인터에 할당합니다.
    • 따라서 현재 요소를 제거합니다 (7 행).

위의 특별한 경우가 있습니다-반복이 시작될 때 이전 항목 ( previs NULL) 이 없으므로 목록에서 첫 번째 항목을 제거하려면 head 자체를 수정해야합니다 (9 행).

Linus가 말한 것은 이전 요소를 포인터가 아닌 포인터에 대한 포인터로 만들어 위 코드를 단순화 할 수 있다는 것 입니다.
그런 다음 코드는 다음과 같습니다.

list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry->val == to_remove)
        *pp = entry->next;

    pp = &entry->next;
    entry = entry->next;
}

위의 코드는 이전 변형과 매우 유사하지만 시작 부분 pp이 아니기 때문에 목록의 첫 번째 요소의 특수 사례를 더 이상 감시 할 필요가없는 방법에 주목 NULL하십시오. 간단하고 영리합니다.

또한 그 스레드의 누군가는 이것이 더 좋은 이유 *pp = entry->next는 원자 적이기 때문에 논평했다고 말했습니다 . 그것은 확실히 원자가 아닙니다 .
위의 표현식에는 두 개의 역 참조 연산자 ( *및 ->)와 하나의 대입이 포함되며이 세 가지 중 어느 것도 원자가 아닙니다.
이것은 일반적인 오해하지만, C에서 슬프게도 거의 아무것도 이제까지 원자 것으로 간주되어서는 안 합니다 (포함 ++및 --사업자)!


4
이것은 더 잘 이해하는 데 도움이 될 것입니다 -grisha.org/blog/2013/04/02/linus-on-understanding- 포인터
— kumar

@kumar 좋은 참조. 더 많은 가시성을 위해 답변에 포함 시켰습니다.
— VonC

이 비디오 는 당신의 모범을 이해하는 데 필수적이었습니다. 특히 나는 메모리 다이어그램을 그리고 프로그램의 진행 상황을 추적 할 때까지 혼란스럽고 호전적이었다. 그것은 여전히 ​​나에게 다소 신비한 것 같습니다.
— Chris

@Chris Great video, 언급 해 주셔서 감사합니다! 더 많은 가시성을 제공하기 위해 귀하의 의견을 답변에 포함 시켰습니다.
— VonC

14

대학에서 프로그래밍 과정에 대한 포인터를 다룰 때, 그것들을 배우기 시작하는 방법에 대한 두 가지 힌트가 주어졌습니다. 첫 번째는 Binky와 함께 포인터 재미 를 보는 것이었다 . 두 번째는 Lewis Carroll 's Through the-Glass 의 Haddocks 'Eyes 구절 을 생각하는 것이 었습니다

기사는 불안한 목소리로“너는 슬프다.

“매우 길어요?” 앨리스는 그날 많은시를 들었 기 때문에 물었다.

기사는“길이가 길지만 매우 아름답습니다. 내 말을 듣는 사람은 노래를 부르며 눈물을 흘리거나

"아니면 뭐?" 앨리스는 기사가 갑자기 멈 췄기 때문에 말했다.

“그렇지 않으면 그렇지 않습니다. 노래의 이름은 '해독'눈이라고합니다.”

앨리스는“아, 그 노래의 이름이 맞나요?”라고 말했다.

“아니다. 이해가 안 돼요.”기사는 약간의 욕심을 보며 말했다. “이것이 이름입니다. 그 이름은 정말 '노인 노인'입니다.”

“그런데‘그게 노래가 뭐에요?’라고 말해야합니까? " 앨리스는 자신을 수정했습니다.

“아니, 당신은해서는 안됩니다 : 그것은 또 다른 것입니다! 이 노래의 이름은 'Ways And Means'입니다.하지만 그것이 바로 그 노래입니다.”

“그럼 노래가 뭐야?” 이시기에 완전히 당황한 앨리스는 말했다.

기사는“나는 그것에오고 있었다”고 말했다. "노래는 실제로 'A-sitting On A Gate'입니다. 곡은 저 자신의 발명품입니다."


1
나는 그 구절을 두 번 읽어야했다.
— Ruben Steins

이것이 Lewis Carroll이 평범한 작가가 아닌 이유입니다.
— metarose

1
그래서 .. 이렇게 될까요? 이름 -> '노인 세 사람이'-> 전화 -> '대구의 눈'-> 음악 -> 'A-앉아 게이트에'
— tisaconundrum

12

당신은 이것을 읽을 수 있습니다 : 포인터에 포인터

이것이 기본적인 의심을 분명히하는 데 도움이되기를 바랍니다.


7

포인터에 대한 참조가 필요한 경우 예를 들어, 호출 된 함수 내에서 호출 된 함수의 범위에 선언 된 포인터 변수의 값 (주소가 가리키는)을 수정하려는 경우.

단일 포인터를 인수로 전달하면 호출 범위의 원래 포인터가 아닌 포인터의 로컬 복사본이 수정됩니다. 포인터에 대한 포인터를 사용하여 포인터를 수정합니다.


'왜'부분에 대한 설명
— Rana Deep

7

포인터에 대한 포인터를 핸들 이라고도합니다 . 한 가지 용도는 종종 객체를 메모리에서 이동하거나 제거 할 수있는 경우입니다. 종종 객체 의 사용을 잠그고 잠금을 해제하는 책임이 있습니다. 액세스 할 때 움직이지 않도록하는 있습니다.

메모리 제한 환경 (예 : Palm OS)에서 자주 사용됩니다.

computer.howstuffworks.com 링크 >>

www.flippinbits.com 링크 >>


7

이 개념을 더 잘 이해하려면 아래 그림과 프로그램을 고려하십시오 .

이중 포인터 다이어그램

그림에 따라 ptr1 은 변수 num의 주소를 갖는 단일 포인터 입니다 .

ptr1 = #

마찬가지로 ptr2 는 포인터 ptr1 의 주소를 가진 pointer (double pointer)에 대한 포인터 입니다 .

ptr2 = &ptr1;

다른 포인터를 가리키는 포인터를 이중 포인터라고합니다. 이 예에서 ptr2 는 이중 포인터입니다.

위 다이어그램의 값 :

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

예:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

산출:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

5

포인터의 주소 값에 대한 포인터입니다. (내가 끔찍한 일이야)

기본적으로 다른 포인터의 주소 값에 포인터를 전달할 수 있으므로 다음과 같이 하위 함수에서 다른 포인터가 가리키는 위치를 수정할 수 있습니다.

void changeptr(int** pp)
{
  *pp=&someval;
}

죄송합니다. 꽤 나빴습니다. : 음,이, 읽기 시도 codeproject.com/KB/cpp/PtrToPtr.aspx
— 누가 복음 쉐퍼

5

무언가의 주소를 포함하는 변수가 있습니다. 그것은 포인터입니다.

그런 다음 첫 번째 변수의 주소를 포함하는 다른 변수가 있습니다. 그것은 포인터를 가리키는 포인터입니다.


3

포인터에 대한 포인터는 포인터에 대한 포인터입니다.

someType **의 의미있는 예는 2 차원 배열입니다. 하나의 배열에 다른 배열에 대한 포인터로 채워져 있으므로 쓸 때

dpointer [5] [6]

자신의 5 번째 위치에있는 다른 배열에 대한 포인터를 포함하는 배열에 액세스하고 포인터를 가져와 (이름을 fpointer라고 함) 해당 배열을 참조하는 배열의 6 번째 요소 (fpointer [6])에 액세스합니다.


2
포인터에 대한 포인터는 rank2의 배열과 혼동되어서는 안됩니다. 예를 들어 x [5] [6]을 작성하는 배열 int x [10] [10]은 배열의 값에 액세스합니다.
— 피트 Kirkham

이것은 void **가 적절한 예일뿐입니다. 포인터를 가리키는 포인터는 포인터를 가리키는 포인터 일뿐입니다.
— akappa

1

작동 방식 : 다른 포인터를 저장할 수있는 변수입니다.

언제 사용합니까 : 많은 사람들이 그중 하나를 사용하면 함수가 배열을 구성하고 호출자에게 반환하려는 경우입니다.

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
    int *j;
    *i = (int*)malloc ( 2*sizeof(int) );
    **i = 11;  // e.g., newly allocated memory 0x2000 store 11
    j = *i;
    j++;
    *j = 12; ;  // e.g., newly allocated memory 0x2004 store 12

    return 2;
}

int main()
{
    int *i;
    int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
    for ( int j=0; j<n; j++ )
        printf( "roll no = %d \n", i[j] );

    return 0;
}


0

유용한 설명이 너무 많지만 간단한 설명을 찾지 못했습니다.

기본적으로 포인터는 변수의 주소입니다. 짧은 요약 코드 :

     int a, *p_a;//declaration of normal variable and int pointer variable
     a = 56;     //simply assign value
     p_a = &a;   //save address of "a" to pointer variable
     *p_a = 15;  //override the value of the variable

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference)
     printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

또한 유용한 정보는 참조 및 역 참조를 의미하는 주제에서 찾을 수 있습니다.

그리고 포인터가 유용 할 수있을 때 확실하지 않지만 일반적으로 수동 / 동적 메모리 할당-malloc, calloc 등을 수행 할 때 포인터를 사용해야합니다 .

그래서 나는 그것이 문제를 명확히하는 데 도움이되기를 바랍니다 :)

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