여기에 게시 된 많은 질문에서 사람들이 포인터와 포인터 산술 주위를 둘러 볼 때 약간의 근본적인 문제가 있음이 분명합니다.
왜 그런지 궁금합니다. 그들은 실제로 큰 문제를 일으킨 적이 없습니다 (신석기 시대에서 처음으로 그것들에 대해 배웠지 만). 이 질문들에 대한 더 나은 답변을 작성하기 위해, 사람들이 어려워하는 것을 알고 싶습니다.
따라서 포인터로 어려움을 겪고 있거나 최근에 갑자기 “가져왔다”면 문제를 일으킨 포인터의 측면은 무엇입니까?
답변
나는 사람들이 그들의 답변에 너무 깊이 가고 있다고 생각합니다. 스케줄링, 실제 CPU 작동 또는 어셈블리 레벨 메모리 관리에 대한 이해는 실제로 필요하지 않습니다.
가르 칠 때 학생들의 이해에서 다음과 같은 구멍이 가장 일반적인 문제의 원인이라는 것을 알았습니다.
- 힙 대 스택 스토리지. 일반적인 의미 에서조차 얼마나 많은 사람들이 이것을 이해하지 못하는지는 놀랍습니다.
- 스택 프레임. 로컬 변수에 대한 스택의 전용 섹션에 대한 일반적인 개념과 그것이 스택이라는 이유와 함께 반환 위치 숨김, 예외 처리기 세부 정보 및 이전 레지스터와 같은 세부 정보는 누군가가 시도 할 때까지 안전하게 남겨 둘 수 있습니다 컴파일러를 빌드하십시오.
- “메모리는 메모리는 메모리이다”캐스팅은 단지 어떤 버전의 연산자 나 컴파일러가 특정 메모리 청크를 위해 얼마나 많은 공간을 제공하는지 변경합니다. 사람들이 “(기본) 변수 X가 실제로 무엇인가”에 대해 이야기 할 때이 문제를 다루고 있다는 것을 알고 있습니다 .
대부분의 학생들은 일반적으로 현재 범위에서 스택의 로컬 변수 섹션 인 메모리 덩어리의 단순화 된 그림을 이해할 수있었습니다. 일반적으로 다양한 위치에 허구의 주소를 명시 적으로 알려주는 것이 도움이되었습니다.
요약하면, 포인터를 이해하려면 변수와 현대 아키텍처의 변수를 이해해야한다고 말하고 있습니다.
답변
내가 그들과 처음 작업을 시작했을 때 가장 큰 문제는 구문이었습니다.
int* ip;
int * ip;
int *ip;
모두 동일합니다.
그러나:
int* ip1, ip2; //second one isn't a pointer!
int *ip1, *ip2;
왜? 선언의 “포인터”부분은 유형이 아니라 변수에 속하기 때문입니다.
그리고 물건을 역 참조하는 것은 매우 유사한 표기법을 사용합니다.
*ip = 4; //sets the value of the thing pointed to by ip to '4'
x = ip; //hey, that's not '4'!
x = *ip; //ahh... there's that '4'
실제로 포인터를 가져와야 할 때를 제외하고는 앰퍼샌드를 사용하십시오!
int *ip = &x;
일관성을위한 만세!
그런 다음 분명히 바보가되어 얼마나 영리한지 입증하고 많은 라이브러리 개발자가 포인터-포인터-포인터를 사용하고 그 배열을 기대하는 경우 포인터를 전달하지 않는 이유는 무엇입니까? .
void foo(****ipppArr);
이것을 호출하려면 정수 포인터에 대한 포인터에 대한 포인터 배열의 주소가 필요합니다.
foo(&(***ipppArr));
6 개월 안에이 코드를 유지해야 할 때 처음부터 다시 쓰는 것보다이 모든 것이 무엇을 의미하는지 알아 내기 위해 더 많은 시간을 할애하게됩니다. (예, 아마도 그 구문이 틀렸을 것입니다 .C에서 아무것도한지 오래되었습니다. 나는 그것을 놓쳤지만 약간의 질량 학자입니다)
답변
포인터를 제대로 이해하려면 기본 시스템의 아키텍처에 대한 지식이 필요합니다.
오늘날 많은 프로그래머들은 자동차 운전 방법을 알고있는 대부분의 사람들이 엔진에 대해 아무것도 모르는 것처럼 기계 작동 방식을 모릅니다.
답변
포인터를 다룰 때 혼란스러워하는 사람들은 두 캠프 중 하나에 널리 퍼져 있습니다. 나는 둘 다에 있었다.
array[]
군중
이것은 포인터 표기법에서 배열 표기법으로 변환하는 방법을 알지 못하는 군중입니다 (또는 심지어 관련성이 있음조차 알지 못합니다). 다음은 배열 요소에 액세스하는 네 가지 방법입니다.
- 배열 이름을 가진 배열 표기법 (인덱싱)
- 포인터 이름으로 배열 표기법 (인덱싱)
- 포인터 이름이있는 포인터 표기법 (*)
- 배열 이름이있는 포인터 표기법 (*)
int vals[5] = {10, 20, 30, 40, 50};
int *ptr;
ptr = vals;
array element pointer
notation number vals notation
vals[0] 0 10 *(ptr + 0)
ptr[0] *(vals + 0)
vals[1] 1 20 *(ptr + 1)
ptr[1] *(vals + 1)
vals[2] 2 30 *(ptr + 2)
ptr[2] *(vals + 2)
vals[3] 3 40 *(ptr + 3)
ptr[3] *(vals + 3)
vals[4] 4 50 *(ptr + 4)
ptr[4] *(vals + 4)
여기서 아이디어는 포인터를 통해 배열에 액세스하는 것 같습니다. 매우 간단하고 간단 해 보이지만 매우 복잡하고 영리한 작업을 수행 할 수 있습니다. 일부는 경험이없는 C / C ++ 프로그래머가 경험이없는 초보자도 당황하게 할 수 있습니다.
reference to a pointer
및pointer to a pointer
군중
이 차이점을 설명하고 어떤 코드를 인용하고 훔칠 것입니다 🙂
작은 예로, 다음과 같은 것을 발견했을 때 저자가 원하는 것을 정확하게 보는 것은 매우 어려울 수 있습니다.
//function prototype
void func(int*& rpInt); // I mean, seriously, int*& ??
int main()
{
int nvar=2;
int* pvar=&nvar;
func(pvar);
....
return 0;
}
또는 다음과 같이 조금 더 :
//function prototype
void func(int** ppInt);
int main()
{
int nvar=2;
int* pvar=&nvar;
func(&pvar);
....
return 0;
}
그래서 하루가 끝나면이 모든 횡설수설로 무엇을 해결해야합니까? 아무것도.
이제 우리는 ptr-to-ptr과 ref-to-ptr의 구문을 보았습니다. 다른 것보다 장점이 있습니까? 나는 두렵다. 일부 프로그래머의 경우 두 가지 중 하나의 사용법은 개인 취향입니다. ref-to-ptr을 사용하는 일부 사람들은 구문이 “깨끗하다”고 말하지만 ptr-to-ptr 구문을 사용하는 일부 사람들은 ptr-to-ptr 구문을 통해 자신이하는 일을 읽는 사람들에게 더 명확하다고 말합니다.
이러한 복잡성과 참조와의 겉보기 (굵게 보이는) 상호 교환 성은 종종 포인터의 또 다른 경고와 초보자의 오류 인 포인터를 이해하기 어렵게 만듭니다. 그것은 참조 포인터가로 당신을 데려 갈 이유 혼란에 대한 C와 C ++에서 불법 것을 완성을 위하여, 이해하는 것도 중요합니다 lvalue
– rvalue
의미를.
이전 답변에서 언급했듯이 여러 번 사용하면 영리하고 있다고 생각하는이 핫 샷 프로그래머가 ******awesome_var->lol_im_so_clever()
있으며 대부분의 사람들은 때때로 그러한 잔학 행위를 저지르는 죄책감을 느끼지 만 코드는 좋지 않으며 코드를 유지할 수는 없습니다. .
글쎄,이 대답은 내가 기대했던 것보다 길다.
답변
나는 참고 자료의 질과 개인적으로 가르치는 사람들을 비난합니다. C의 대부분의 개념 ( 특히 포인터)은 잘못 가르쳐 져 있습니다. 나는 내 자신의 C 책 ( The Last Thing The World Needs Is The C Book Language on The C Programming Language ) 을 작성하겠다고 계속 위협하고 있지만 그렇게 할 시간이나 인내심은 없습니다. 그래서 나는 여기서 놀고 사람들에게 표준에서 무작위 인용을 던졌습니다.
또한 C가 처음 설계되었을 때 일상적인 작업에서 피할 수있는 방법이 없었기 때문에 머신 아키텍처를 매우 세부적인 수준으로 이해 했다고 가정 했습니다 (메모리가 너무 빡빡하고 프로세서가 너무 느 렸습니다) 작성한 내용이 성능에 어떤 영향을 미치는지 이해해야했습니다.)
답변
Joel Spolsky의 사이트 인 The Perils of JavaSchools 에 포인터가 어렵다는 개념을 뒷받침하는 훌륭한 기사가 있습니다 .
[면책 조항-본인은 Java-hater 자체가 아닙니다 .]
답변
“아래에있는”지식에 근거하지 않으면 대부분의 것들을 이해하기가 더 어렵습니다. CS를 가르쳤을 때 학생들이 매우 간단한 “머신”, 즉 십진 레지스터와 십진 주소로 구성된 메모리를 가진 십진 opcode를 가진 시뮬레이션 된 십진 컴퓨터를 프로그래밍하는 것이 훨씬 쉬워졌습니다. 예를 들어, 총계를 얻기 위해 일련의 숫자를 추가하는 매우 짧은 프로그램을 넣었습니다. 그런 다음 그들은 무슨 일이 일어나고 있는지보기 위해 한 발짝 씩 걸었습니다. “enter”키를 누르고 “빠른”실행을 볼 수 있습니다.
나는 거의 모든 사람들이 왜 그렇게 기본을 얻는 것이 유용한 지 궁금합니다. 프로그래밍하는 법을 모른다는 것이 어떤 것인지 잊어 버립니다. 이러한 장난감 컴퓨터를 가지고 노는 것은 계산이 단계별 프로세스라는 아이디어, 소수의 기본 프리미티브를 사용하여 프로그램을 구축하는 아이디어, 메모리 개념과 같이 프로그래밍 할 수없는 개념을 제자리에 둡니다. 변수의 주소 또는 이름이 포함 된 숫자와 구별되는 숫자가 저장되는 위치로 변수. 프로그램에 들어가는 시간과 프로그램이 실행되는 시간에는 차이가 있습니다. 매우 간단한 프로그램, 루프 및 서브 루틴, 배열, 순차적 I / O, 포인터 및 데이터 구조와 같은 일련의 “스피드 범프”를 교차하는 것으로 프로그래밍하는 것을 배우는 것을 좋아합니다.
마지막으로 C에 도달하면 포인터가 혼란스럽고 K & R이이를 잘 설명해주었습니다. 내가 C에서 배운 방식은 오른쪽에서 왼쪽으로 읽는 방법을 아는 것입니다. int *p
내 머리에서 볼 때처럼 “”를 p
가리 킵니다 int
. C는 어셈블리 언어에서 한 단계 올라간 것으로 발명되었으며 그것이 제가 좋아하는 것입니다. “접지”에 가깝습니다. 다른 접지와 마찬가지로 포인터는 접지가 없으면 이해하기 어렵습니다.