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-GlassHaddocks '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.