Programing

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

crosscheck 2020. 5. 31. 10:04
반응형

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


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


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 *.
  • 여기에 나열하기에는 너무 많은 다른 이유가 있습니다.

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 차원 배열을 사용하게됩니다.


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 .


kumarGrisha Trubetskoy가 설명 하는 블로그 게시물 " 포인터 이해에 대한 Linus " 의 의견을 지적 합니다.

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

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에서 슬프게도 거의 아무것도 이제까지 원자 것으로 간주되어서는 안 합니다 (포함 ++--사업자)!


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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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


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

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

computer.howstuffworks.com 링크 >>

www.flippinbits.com 링크 >>


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

이중 포인터 다이어그램

그림에 따라 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

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

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


it's a pointer to the pointer's address value. (that's terrible I know)

basically, it lets you pass a pointer to the value of the address of another pointer, so you can modify where another pointer is pointing from a sub function, like:

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

A pointer to pointer is, well, a pointer to pointer.

A meaningfull example of someType** is a bidimensional array: you have one array, filled with pointers to other arrays, so when you write

dpointer[5][6]

you access at the array that contains pointers to other arrays in his 5th position, get the pointer (let fpointer his name) and then access the 6th element of the array referenced to that array (so, fpointer[6]).


How it works: It is a variable that can store another pointer.

When would you use them : Many uses one of them is if your function wants to construct an array and return it to the caller.

//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;
}

포인터가 작동하는 방식을 설명하는 5 분짜리 비디오를 만들었습니다.

https://www.youtube.com/watch?v=3X-ray3tDjQ

포인터 버킷


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

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

     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 등을 수행 할 때 포인터를 사용해야합니다 .

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

참고 URL : https://stackoverflow.com/questions/897366/how-do-pointer-to-pointers-work-in-c

반응형