이중 간접 지정을 사용하는 이유는 무엇입니까? 또는 왜 포인터를 포인터로 사용합니까?


272

C에서 이중 간접을 사용해야하는 시점은 언제입니까? 누구나 예를 들어 설명 할 수 있습니까?

내가 아는 것은 이중 간접 지시가 포인터에 대한 포인터라는 것입니다. 포인터에 대한 포인터가 필요한 이유는 무엇입니까?


49
조심해; "double pointer"라는 문구는 type을 가리 킵니다 double*.
Keith Thompson

참고 :이 질문에 대한 답변은 C와 C ++에서 다릅니다.이 아주 오래된 질문에 c + 태그를 추가하지 마십시오.
BЈовић

답변:


479

문자 목록 (단어)을 원한다면 char *word

단어 목록 (문장)을 원한다면 char **sentence

문장 목록 (독백)을 원한다면 char ***monologue

독백 (전기) 목록을 원한다면 char ****biography

전기 목록 (바이오 라이브러리)을 원한다면 char *****biolibrary

바이오 라이브러리 목록을 원한다면 (??? lol) char ******lol

... ...

예, 이것이 최선의 데이터 구조가 아닐 수도 있음을 알고 있습니다


매우 지루한 lol을 사용한 사용 예

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

산출:

내 lol에있는 총 단어 : 243

6
a arr[a][b][c]가 아닌 것을 지적하고 싶었 습니다 ***arr. 포인터의 포인터는 참조의 참조를 사용하지만 arr[a][b][c]행의 주요 순서로 일반적인 배열로 저장됩니다.
MCCCS 2016 년

170

함수 인수로 함수에 전달 된 포인터의 값을 변경하려면 포인터에 대한 포인터가 필요합니다.

간단히 말해서 함수 호출 외부에서도 메모리 할당 또는 할당을 유지 (또는 변경 유지) 할 때 사용하십시오 **. (따라서 더블 포인터 인수로 그러한 함수를 전달하십시오.)

이것은 좋은 예는 아니지만 기본 사용법을 보여줍니다.

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}

14
만약 할당이 void allocate(int *p)있고 당신이 그것을 불렀다 면 무엇이 다를까요 allocate(p)?
ア レ ッ ク ス

@AlexanderSupertramp 예. 코드는 segfault입니다. Silviu의 답변을 참조하십시오.
Abhishek

@Asha assign (p)와 assign (& p)의 차이점은 무엇입니까?
user2979872

1
@Asha-포인터를 반환 할 수 없습니까? 이를 무효로 유지해야하는 경우이 시나리오의 실제 사용 사례는 무엇입니까?
Shabirmean

91
  • 포인터가 있다고 가정 해 봅시다. 그 값은 주소입니다.
  • 하지만 이제 그 주소를 변경하고 싶습니다.
  • 당신은 할 수 있습니다. 을 수행 pointer1 = pointer2하면 pointer1에 pointer2의 주소를 제공합니다.
  • 그러나! 함수 내에서이를 수행하고 함수가 완료된 후에도 결과를 유지하려면 추가 작업이 필요합니다. pointer1을 가리 키기 위해 새로운 pointer3이 필요합니다. pointer3를 함수에 전달하십시오.

  • 여기 예가 있습니다. 아래 출력을 먼저 이해하십시오.

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

출력은 다음과 같습니다 ( 이를 먼저 읽으십시오 ).

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 

4
이것은 훌륭한 답변이며 이중 포인터의 목적과 유용성을 시각화하는 데 실제로 도움이되었습니다.
저스틴

1
@Justin이 답변 위에 내 답변을 확인 했습니까? 그것의 청소기 :)
브라이언 조셉 Spinos

10
큰 대답은 <code> void cant_change (int * x, int * z) </ code> 매개 변수가 a 및 f 포인터와 같이 초기화 된 새로운 로컬 범위 포인터이므로 실패한다는 설명이 부족합니다. a와 f)와 동일합니다.
Pedro Reis

1
단순한? 정말? ;)
alk

1
이 답변은 실제로 포인터 포인터의 가장 일반적인 사용 중 하나를 설명합니다. 감사합니다!
tonyjosi

48

다음 예제에 대한 단일 포인터 (예 : alloc1 ())를 사용하는 경우 Asha의 응답에 추가 하면 함수 내에 할당 된 메모리에 대한 참조가 손실됩니다.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

이것이 발생하는 이유 alloc1는 포인터가 값으로 전달되기 때문입니다. 따라서 malloc내부 호출 결과에 다시 할당 alloc1되면 변경 내용이 다른 범위의 코드와 관련이 없습니다.


1
p가 정적 정수 포인터 인 경우 어떻게됩니까? 세그먼테이션 오류가 발생했습니다.
kapilddit

free(p)충분하지 않습니다, 당신 if(p) free(*p)은뿐만 아니라
Shijing Lv

@ShijingLv : 제 *p가로 평가 int, 10의 값을 보관 유지하는이 통과 int() 확보는`나쁜 생각이다.
alk

할당이 수행 alloc1()되면 메모리 누수가 발생합니다. 해제 된 포인터 값은 함수에서 리턴하면 유실됩니다.
alk

C에서 malloc의 결과를 캐스팅 할 필요는 없습니다!
alk

23

아래에 요약 된 것처럼 이 블로그 게시물 에서 오늘 좋은 예를 보았습니다 .

링크 된 목록에 노드 구조가 있다고 가정하십시오.

typedef struct node
{
    struct node * next;
    ....
} node;

이제 remove_if제거 기준 rm을 인수 중 하나로 받아들이고 링크 된 목록을 순회 하는 함수 를 구현하려고 합니다. 항목이 기준 ()을 만족하면 rm(entry)==true해당 노드가 목록에서 제거됩니다. 결국, remove_if연결된 목록의 헤드 (원래 헤드와 다를 수 있음)를 반환합니다.

당신은 쓸 수 있습니다

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

당신의 for루프로. 이중 포인터prev없는 메시지는 포인터 를 재구성 하기 위해 변수를 유지 하고 두 가지 경우를 처리해야한다는 것입니다.

그러나 이중 포인터를 사용하면 실제로 쓸 수 있습니다.

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

가리키는 항목을 직접 수정할 수 있으므로 prev지금 필요하지 않습니다 .prev->next

더 명확하게하기 위해 코드를 조금만 따라 봅시다. 제거하는 동안 :

  1. if entry == *head: 이제 *head (==*curr) = *head->next- head새로운 표제 노드의 포인터를 가리 킵니다. 님 head의 콘텐츠를 새 포인터 로 직접 변경하면됩니다 .
  2. 경우 entry != *head: 유사하게, *curr무엇 prev->next에 지금 점에 지적하고 entry->next.

어떤 경우 든 더블 포인터를 사용하여 포인터를 통합 된 방식으로 재구성 할 수 있습니다.


22

1. 기본 개념-

다음과 같이 선언하면 :-

1. char * ch-(문자 포인터라고 함)
-ch는 단일 문자의 주소를 포함합니다.
-(* ch)는 ​​문자 값을 참조하지 않습니다.

2. char ** ch-
'ch'는 문자 포인터 배열의 주소를 포함합니다. (1에서와 같이)
'* ch'는 단일 문자의 주소를 포함합니다. (선언의 차이로 인해 1과 다릅니다.)
(** ch)는 ​​문자의 정확한 값을 참조하지 않습니다.

포인터를 더 추가하면 데이터 유형의 차원이 문자에서 문자열, 문자열 배열 등으로 확장됩니다. 1d, 2d, 3d 행렬과 연관시킬 수 있습니다.

따라서 포인터 사용법은 선언 방법에 따라 다릅니다.

다음은 간단한 코드입니다.

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. 더블 포인터의 또 다른 응용-
(이것은 참조로 패스를 커버 할 것입니다)

함수에서 문자를 업데이트한다고 가정하십시오. 다음을 시도하면 :-

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

출력은 AA가됩니다. 함수에 "Passed By Value"가 있으므로 작동하지 않습니다.

올바른 방법은-

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

이제 문자 대신 문자열을 업데이트하기 위해이 요구 사항을 확장하십시오.
이를 위해 함수에서 매개 변수를 이중 포인터로 받아야합니다.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

이 예제에서 메소드는 문자열의 값을 업데이트하기 위해 매개 변수로 이중 포인터를 예상합니다.


#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } 그러나 이중 포인터를 사용하지 않고도 할 수 있습니다.
kumar

" char ** ch- 'ch'는 문자 포인터 배열의 주소를 포함합니다. "아니오, char포인터 배열의 첫 번째 요소 주소를 포함합니다 . 의 배열에 대한 포인터 char*는 예를 들어 다음과 같이 입력 됩니다 .에 대한 42 배열의 포인터를 char(*(*p)[42])정의 p합니다 char.
alk

마지막 스 니펫이 완전히 손상되었습니다. 우선 : *str = ... str정의되지 않은 동작을 호출하는 초기화되지 않은 참조가 있습니다.
alk

이것은 malloc(sizeof(char) * 10);10 포인터를위한 공간을 할당하지 않고 char10 char만을 위한 공간을 할당 합니다.
alk

이 루프 for(i=0;i<10;i++) { str = ... 는 인덱스를 사용하지 못합니다 i.
alk

17

포인터에 대한 포인터는 또한 메모리 사이의 "핸들"로서 유용하며, 함수들 사이의 "핸들"주위를 이동하여 재배치 가능한 메모리로 전달하려고합니다. 이는 기본적으로 함수가 핸들 변수 내부의 포인터가 가리키는 메모리를 변경할 수 있으며 핸들을 사용하는 모든 함수 또는 객체가 새로 재배치 된 (또는 할당 된) 메모리를 올바르게 가리 킵니다. 라이브러리는 "불투명 한"데이터 형식으로이 작업을 수행하는 것과 같습니다. 즉, 데이터 형식은 메모리가 지적한 작업에 대해 걱정할 필요가 없었으며, 단순히 "핸들"을 해당 메모리에서 일부 작업을 수행하는 라이브러리 기능 ...

예를 들어 :

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

도움이 되었기를 바랍니다,

제이슨


핸들 유형이 부호없는 char ** 인 이유는 무엇입니까? void **도 잘 작동합니까?
코너 클락

5
unsigned char원시 바이트로 표시되는 이진 데이터에 대한 포인터를 저장하기 때문에 특히 사용됩니다. 사용 void하려면 어느 시점에서 캐스트가 필요하며 일반적으로 수행중인 작업의 의도만큼 읽을 수 없습니다.
Jason

7

이전에 여러 번 본 적이있는 간단한 예

int main(int argc, char **argv)

두 번째 매개 변수에는 char에 대한 포인터를 가리키는 포인터가 있습니다.

포인터 표기법 ( char* c)과 배열 표기법 ( char c[])은 함수 인수에서 서로 바꿔 사용할 수 있습니다. 그래서 당신은 또한 쓸 수 있습니다 char *argv[]. 즉 char *argv[]char **argv교환 할 수있다.

위의 내용은 실제로 문자 시퀀스 배열 (시작시 프로그램에 제공되는 명령 행 인수)입니다.

참조 이 답변 위의 함수 서명에 대한 자세한 내용을.


2
"포인터 표기법 ( char* c) 어레이 표기법 ( char c[]) 상호 교환" (그리고 정확히 같은 의미를 갖는다) 함수 인수하여 . 그러나 외부 함수 인수와는 다릅니다.
pmg

6

문자열은 이중 포인터를 사용하는 좋은 예입니다. 문자열 자체는 포인터이므로 문자열을 가리킬 때마다 이중 포인터가 필요합니다.


5

예를 들어, 메모리를 확보 할 때 포인터를 널로 설정했는지 확인하고 싶을 수 있습니다.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

이 함수를 호출하면 포인터의 주소로 호출됩니다

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

이제 myMemoryNULL로 설정되어 있으며 재사용하려는 시도는 매우 잘못되었습니다.


1
그것은해야 if(*memory)하고free(*memory);
아샤

1
좋은 점, 뇌와 키보드 사이의 신호 손실. 좀 더 이해하기 쉽게 편집했습니다.
Jeff Foster

왜 우리는 다음을 할 수 없습니까 ... void safeFree (void * memory) {if (memory) {free (memory); 메모리 = NULL; }}
Peter_pk

@Peter_pk 메모리를 null에 할당하면 참조가 아닌 값으로 포인터를 전달했기 때문에 도움이되지 않습니다 (따라서 포인터에 대한 포인터의 예).
Jeff Foster

2

예를 들어 비 연속 데이터에 무작위로 액세스하려는 경우.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

-C에서

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

포인터 p배열을 가리키는 포인터를 저장합니다. 각 포인터는 데이터 조각을 가리 킵니다.

경우 sizeof(T)큰 그것의 (즉, malloc을 사용) 연속 블록을 할당하는 것이 가능하지 않을 수 있습니다 sizeof(T) * n바이트.


1
C에서 malloc의 결과를 캐스팅 할 필요는 없습니다!
alk

2

내가 지속적으로 사용하는 것 중 하나는 객체 배열이 있고 다른 필드별로 조회 (이진 검색)를 수행해야 할 때입니다.
원래 배열을 유지합니다 ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

그런 다음 객체에 대한 정렬 된 포인터 배열을 만듭니다.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

필요한만큼 정렬 된 포인터 배열을 만든 다음 정렬 된 포인터 배열에서 이진 검색을 사용하여 필요한 데이터로 필요한 개체에 액세스 할 수 있습니다. 객체의 원래 배열은 정렬되지 않은 채로있을 수 있지만 각 포인터 배열은 지정된 필드를 기준으로 정렬됩니다.


2

왜 더블 포인터인가?

목적은 함수를 사용하여 studentA가 가리키는 것을 변경하는 것입니다.

#include <stdio.h>
#include <stdlib.h>


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */

1
C에서 malloc의 결과를 캐스팅 할 필요는 없습니다!
alk

2

다음은 함수를 사용하여 포인터가 객체를 가리 키도록 설정 하려면 포인터에 대한 포인터가 필요 하다는 것을 보여주는 매우 간단한 C ++ 예제입니다 . 그렇지 않으면 포인터가 계속 null로 되돌아갑니다 .

(C ++ 답변이지만 C와 동일하다고 생각합니다.)

(또한 참조 : Google ( "pass by value c ++") = "기본적으로 C ++의 인수는 값으로 전달됩니다. 인수가 값으로 전달되면 인수의 값이 함수의 매개 변수에 복사됩니다.")

따라서 포인터 b를 문자열과 동일하게 설정하려고 합니다 a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

라인에서 무슨 일이 Function_1(&a, b);?

  • &main::a(주소) 의 "값"이 매개 변수에 복사됩니다 std::string* Function_1::a. 따라서 Function_1::a문자열에 대한 포인터 (즉, 메모리 주소) main::a입니다.

  • main::b(메모리의 주소) 의 "값"이 매개 변수에 복사됩니다 std::string* Function_1::b. 따라서 메모리에이 주소 중 2 개가 있으며 모두 널 포인터입니다. 라인 b = a;에서 로컬 변수 Function_1::b는 동일 Function_1::a(= &main::a) main::b으로 변경 되지만 변수 는 변경되지 않습니다. 호출 한 후 Function_1, main::b여전히 널 포인터이다.

라인에서 무슨 일이 Function_2(&a, &b);?

  • a변수 처리는 동일합니다. 함수 내 Function_2::a에서 문자열의 주소입니다 main::a.

  • 그러나 변수 b는 이제 포인터에 대한 포인터로 전달됩니다. &main::b( 포인터주소)의 "값"이에 main::b복사됩니다 std::string** Function_2::b. 따라서 Function_2 내에서 *Function_2::b액세스 및 수정하는 것처럼 이것을 역 참조 합니다 main::b. 따라서 라인 *b = a;은 실제로 main::b(주소)를 Function_2::a(= address of main::a)와 동일하게 설정 하고 있습니다.

객체를 수정하기 위해 함수를 사용하려면 객체 또는 주소 (포인터), 해당 객체에 대한 포인터를 전달해야합니다. 로컬 사본이 작성되므로 실제로 전달 하는 것은 호출 범위에서 수정할 수 없습니다.

(매개 변수가 참조와 같은 참조 인 경우는 예외입니다 std::string& a. 그러나 일반적으로 이것들은 const입니다. 일반적으로 호출하는 f(x)경우 x, 개체 인 경우 수정 f 하지 않을 것이라고 가정 할 수 있습니다 x. 그러나 x포인터 인 경우 에는 에서 가리키는 객체를 수정할 f 있다고 가정합니다 x.)


C 질문에 대답하는 C ++ 코드는 최선의 아이디어가 아닙니다.
alk

1

파티에 조금 늦었지만 희망적으로 이것은 누군가를 도울 것입니다.

C 배열에서는 항상 스택에 메모리를 할당하므로 실행이 현재 블록의 끝에 도달하면 스택에 할당 된 메모리가 자동으로 해제되므로 함수는 (정적이 아닌) 배열을 반환 할 수 없습니다. 2 차원 배열 (예 : 행렬)을 처리하고 행렬을 변경하고 반환 할 수있는 몇 가지 함수를 구현하려는 경우 실제로 성가시다. 이를 위해 포인터 대 포인터를 사용하여 동적으로 할당 된 메모리가있는 행렬을 구현할 수 있습니다.

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

그림은 다음과 같습니다.

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

이중 포인터 대 이중 포인터 A는 요소가 이중 포인터 자체 인 메모리 블록의 첫 번째 요소 A [0]을 가리 킵니다. 이 이중 포인터를 행렬의 행으로 상상할 수 있습니다. 이것이 모든 이중 포인터가 double 유형의 num_cols 요소에 메모리를 할당하는 이유입니다. 또한 A [i]는 i 번째 행을 가리키고, 즉 A [i]는 A [i] [0]을 가리키며 i 번째 행에 대한 메모리 블록의 첫 번째 이중 요소입니다. 마지막으로 A [i] [j]를 사용하여 i 번째 행과 j 번째 열의 요소에 쉽게 액세스 할 수 있습니다.

사용법을 보여주는 완전한 예는 다음과 같습니다.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

0

나는 일을 위해 무언가를 프로그래밍하는 동안 오늘 이중 포인터를 사용 했으므로 왜 우리가 그것들을 사용해야했는지 대답 할 수 있습니다 (실제로 이중 포인터를 사용해야했던 것은 처음입니다). 일부 구조의 멤버 인 버퍼에 포함 된 프레임의 실시간 인코딩을 처리해야했습니다. 엔코더에서는 이러한 구조 중 하나에 대한 포인터를 사용해야했습니다. 문제는 포인터가 다른 스레드의 다른 구조를 가리 키도록 변경되었다는 것입니다. 인코더에서 현재 구조를 사용하려면 다른 스레드에서 수정 된 포인터를 가리 키기 위해 이중 포인터를 사용해야했습니다. 적어도 우리에게는이 접근법을 취해야한다는 것이 처음에는 분명하지 않았습니다. 프로세스에서 많은 주소가 인쇄되었습니다 :)).

응용 프로그램의 다른 위치에서 변경된 포인터를 작업 할 때는 이중 포인터를 사용해야합니다. 또한 반환 및 주소를 지정하는 하드웨어를 처리 할 때 이중 포인터가 필수임을 알 수 있습니다.


0

변수의 수정 값과 포인터 수정 값을 비교하십시오 .

#include <stdio.h>
#include <stdlib.h>

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

이것은 포인터가 호출 된 함수 (단일 링크 된 목록에서 사용)에 의해 수정 될 때 포인터 값을 반환하지 않도록 도와줍니다.

OLD (나쁜) :

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

새로운 (더 나은) :

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.