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 **
가지므로 포인터에 대한 포인터입니다.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
단순화를 적용하면 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 행).
위의 특별한 경우가 있습니다-반복이 시작될 때 이전 항목 (
prev
isNULL
) 이 없으므로 목록에서 첫 번째 항목을 제거하려면 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-Glass 의 Haddocks 'Eyes 구절 을 생각하는 것이 었습니다
기사는 불안한 목소리로“너는 슬프다.
“매우 길어요?” 앨리스는 그날 많은시를 들었 기 때문에 물었다.
기사는“길이가 길지만 매우 아름답습니다. 내 말을 듣는 사람은 노래를 부르며 눈물을 흘리거나
"아니면 뭐?" 앨리스는 기사가 갑자기 멈 췄기 때문에 말했다.
“그렇지 않으면 그렇지 않습니다. 노래의 이름은 '해독'눈이라고합니다.”
앨리스는“아, 그 노래의 이름이 맞나요?”라고 말했다.
“아니다. 이해가 안 돼요.”기사는 약간의 욕심을 보며 말했다. “이것이 이름입니다. 그 이름은 정말 '노인 노인'입니다.”
“그런데‘그게 노래가 뭐에요?’라고 말해야합니까? " 앨리스는 자신을 수정했습니다.
“아니, 당신은해서는 안됩니다 : 그것은 또 다른 것입니다! 이 노래의 이름은 'Ways And Means'입니다.하지만 그것이 바로 그 노래입니다.”
“그럼 노래가 뭐야?” 이시기에 완전히 당황한 앨리스는 말했다.
기사는“나는 그것에오고 있었다”고 말했다. "노래는 실제로 'A-sitting On A Gate'입니다. 곡은 저 자신의 발명품입니다."
포인터에 대한 포인터를 핸들 이라고도합니다 . 한 가지 용도는 종종 객체를 메모리에서 이동하거나 제거 할 수있는 경우입니다. 종종 객체 의 사용을 잠그고 잠금을 해제하는 책임이 있습니다. 액세스 할 때 움직이지 않도록하는 있습니다.
메모리 제한 환경 (예 : Palm OS)에서 자주 사용됩니다.
이 개념을 더 잘 이해하려면 아래 그림과 프로그램을 고려하십시오 .
그림에 따라 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 = #
// 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
포인터의 주소 값에 대한 포인터입니다. (내가 끔찍한 일이야)
기본적으로 다른 포인터의 주소 값에 포인터를 전달할 수 있으므로 다음과 같이 하위 함수에서 다른 포인터가 가리키는 위치를 수정할 수 있습니다.
void changeptr(int** pp)
{
*pp=&someval;
}
포인터에 대한 포인터는 포인터에 대한 포인터입니다.
someType **의 의미있는 예는 2 차원 배열입니다. 하나의 배열에 다른 배열에 대한 포인터로 채워져 있으므로 쓸 때
dpointer [5] [6]
자신의 5 번째 위치에있는 다른 배열에 대한 포인터를 포함하는 배열에 액세스하고 포인터를 가져와 (이름을 fpointer라고 함) 해당 배열을 참조하는 배열의 6 번째 요소 (fpointer [6])에 액세스합니다.
작동 방식 : 다른 포인터를 저장할 수있는 변수입니다.
언제 사용합니까 : 많은 사람들이 그중 하나를 사용하면 함수가 배열을 구성하고 호출자에게 반환하려는 경우입니다.
//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;
}
유용한 설명이 너무 많지만 간단한 설명을 찾지 못했습니다.
기본적으로 포인터는 변수의 주소입니다. 짧은 요약 코드 :
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 등을 수행 할 때 포인터를 사용해야합니다 .
그래서 나는 그것이 문제를 명확히하는 데 도움이되기를 바랍니다 :)