사람들이 C 포인터에 대해 어려워하는 점은 무엇인가?
여기에 게재된 질문의 수를 보면, 사람들이 포인터와 포인터 산수를 이해할 때 매우 근본적인 문제를 가지고 있다는 것이 명백하다.
왜 그런지 궁금해요.신석기 시대에 처음 배웠지만, 그들은 나에게 큰 문제를 일으킨 적이 없다.이 질문들에 대한 더 나은 답을 쓰기 위해서, 저는 사람들이 어떤 것을 어려워하는지 알고 싶습니다.
그렇다면, 여러분이 포인터에 어려움을 겪고 있거나, 최근에 갑자기 "알았다"고 한다면, 여러분에게 문제를 일으킨 포인터의 측면은 무엇이었을까요?
제가 처음 그들과 일을 시작했을 때 가장 큰 문제는 구문이었습니다.
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);
이것을 호출하려면, 나는 ints의 포인터에 대한 포인터 배열의 주소가 필요합니다.
foo(&(***ipppArr));
6개월 안에 이 코드를 유지해야 할 때는 처음부터 다시 쓰는 것보다 더 많은 시간을 들여 이 모든 것이 무엇을 의미하는지 알아낼 것입니다.(아마도 이 구문을 틀렸을 것입니다.C에서 뭔가를 한 지 꽤 지났습니다.좀 그립긴 한데, 좀 매소키스트 같기도 하고)
포인터를 다룰 때, 혼란스러운 사람들은 두 진영 중 하나에 속합니다.둘 다 해봤어요.
그array[]
군중
이들은 포인터 표기법에서 배열 표기법으로 변환하는 방법을 알지 못하거나 심지어 그들이 관련이 있는지조차 알지 못하는 사람들입니다.어레이의 요소에 액세스 하는 4가지 방법은 다음과 같습니다.
- 배열 이름을 사용한 배열 표기법(예:
- 포인터 이름을 사용한 배열 표기법(표준)
- 포인터 이름을 사용한 포인터 표기법(*)
- 배열 이름을 사용한 포인터 표기법(*)
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()
그리고 우리들 대부분은 가끔 그런 잔혹행위를 쓴 것에 대해 죄를 지었을지도 모릅니다. 하지만 그것은 좋은 코드가 아닙니다. 그리고 확실히 유지보수가 불가능합니다.
이 답변은 제가 기대했던 것보다 길었습니다...
Joel Spolsky의 사이트 - The Perils of Java Schools에는 포인터가 어렵다는 개념을 뒷받침하는 훌륭한 기사가 있습니다.
[제소자 - 저는 자바 자체를 싫어하는 사람이 아닙니다]
대부분의 사물은 "내부"에 있는 지식에 기초하지 않으면 이해하기 어렵습니다.CS를 가르쳤을 때, 제가 학생들에게 매우 간단한 "기계"를 프로그래밍하기 시작했을 때, 그것은 훨씬 더 쉬워졌습니다. 이 컴퓨터는 10진수 레지스터와 10진수 주소로 구성된 10진수 연산 코드를 가진 시뮬레이션된 10진수 컴퓨터입니다.예를 들어, 합계를 얻기 위해 일련의 숫자를 더하는 매우 짧은 프로그램을 투입합니다.그리고 나서 그들은 무슨 일이 일어나는지 보기 위해 한 발짝씩 움직였다."Enter" 키를 누른 채로 "fast" 동작하는 것을 볼 수 있습니다.
나는 SO의 거의 모든 사람들이 왜 그렇게 기본적인 것을 하는 것이 유용한지 궁금해 할 것이라고 확신한다.우리는 프로그래밍하는 법을 모르는 것이 어땠는지 잊는다.그러한 장난감 컴퓨터를 가지고 노는 것은 컴퓨터 연산이 단계별 과정이라는 생각, 프로그램을 구축하기 위해 소수의 기본 원소를 사용하는 것, 그리고 숫자가 저장되는 장소로서 기억 변수의 개념과 같이 당신이 프로그램 없이 할 수 없는 개념들을 가지고 있다.그 안에 들어 있는 숫자.프로그램을 시작하는 시간과 "실행"하는 시간 사이에는 차이가 있습니다.프로그램 학습은 매우 간단한 프로그램, 루프와 서브루틴, 어레이, 순차 I/O, 포인터 및 데이터 구조 등 일련의 "속도 범프"를 교차하는 것과 비슷합니다.이 모든 것은 컴퓨터가 실제로 밑에서 무엇을 하고 있는지를 참조함으로써 훨씬 배우기 쉽습니다.
마지막으로, K&R이 잘 설명했지만, C에 도달하면, 포인터는 혼란스럽습니다.C에서 배운 방법은 읽는 법을 아는 것이었습니다.오른쪽에서 왼쪽으로.내가 봤을 때처럼int *p
내 머릿속에서는 이렇게 말한다.p
을 가리키다int
"C는 어셈블리 언어에서 한 단계 더 발전된 것으로서 발명되었습니다.그것이 제가 좋아하는 것입니다.그 "그라운드"에 가깝습니다.포인터는 다른 어떤 것과 마찬가지로 기초가 없으면 이해하기 어렵습니다.
나는 K&R의 설명을 읽을 때까지 힌트를 얻지 못했다.그 시점까지, 조언들은 말이 되지 않았다."지침을 배우지 마라, 혼란스럽고 머리를 다치고 동맥류를 일으킬 것이다"라는 말을 많이 읽어서 오랫동안 피하다가 불필요한 개념의 분위기를 만들어냈다.
그렇지 않으면, 주로 제가 생각했던 것은, 도대체 왜 그 가치를 얻기 위해 후프를 거쳐야 하는 변수를 원하느냐는 것이었습니다. 그리고 만약 당신이 그것에 무언가를 할당하고 싶다면, 그 변수에 가치를 넣기 위해 이상한 것들을 해야 했습니다.변수의 요점은 가치를 저장하는 것이라고 생각했습니다.그래서 왜 누군가가 그것을 복잡하게 만들고 싶은지는 제 능력 밖입니다."그래서 포인터로 연산자를 사용하여 값을 구해야 한다는 건가요? 그게 무슨 바보 같은 변수냐"고 생각했다.무의미해, 말장난은 의도하지 않았어.
그것이 복잡한 이유는 포인터가 무언가에 대한 주소라는 것을 이해하지 못했기 때문이다.주소라고 하는 것, 다른 것에 대한 주소가 포함되어 있는 것, 그리고 그 주소를 조작해 유익한 일을 할 수 있는 것을 설명하면, 혼란을 해소할 수 있다고 생각합니다.
PC의 포트에 액세스/수정하기 위해 포인터를 사용해야 하는 클래스, 서로 다른 메모리 위치를 지정하기 위해 포인터 산수를 사용해야 하는 클래스, 그리고 그 인수를 수정한 더 복잡한 C-코드를 보면 포인터가 무의미하다는 생각이 들지 않습니다.
기계 코드, 조립, RAM 내의 항목 및 데이터 구조를 나타내는 방법에 대한 소개와 함께 기계 레벨의 견고한 기반이 필요하다고 생각합니다.약간의 시간, 약간의 숙제나 문제 해결 연습, 그리고 약간의 생각이 필요하다.
하지만 만약 어떤 사람이 처음에 고급 언어를 안다면, 목수는 도끼를 사용한다. 원자를 쪼개야 하는 사람은 다른 것을 사용한다.목수인 사람이 필요하고, 원자를 연구하는 사람도 있고, 고급 언어를 아는 사람은 포인터에 대해 2분 정도 설명을 받고, 포인터 산술, 포인터에 대한 포인터, 가변 크기 문자열에 대한 포인터 배열, 문자 배열 등을 이해하기를 기대하기는 어렵다.낮은 수준의 견고한 기초가 도움이 됩니다.
나는 사람들이 그들의 대답에 너무 깊이 관여하고 있다고 의심한다.스케줄링, 실제 CPU 작업 또는 어셈블리 수준의 메모리 관리에 대한 이해는 필요하지 않습니다.
교단에 섰을 때 학생들의 이해력에 다음과 같은 결함이 가장 일반적인 문제의 원인임을 알게 되었습니다.
- 히프 스토리지와 스택 스토리지.일반적인 의미에서조차 이것을 이해하지 못하는 사람이 얼마나 많은지 놀라울 따름이다.
- 스택 프레임로컬 변수 전용 스택 섹션의 일반적인 개념과 '스택'인 이유는 다음과 같습니다.반환 위치, 예외 핸들러 세부 정보 및 이전 레지스터와 같은 세부 정보는 컴파일러를 빌드할 때까지 안전하게 남겨둘 수 있습니다.
- "메모리는 메모리입니다." 캐스팅은 연산자의 버전이나 컴파일러가 특정 메모리 청크를 위해 어느 정도의 공간을 제공하는지를 변경합니다.사람들이 "실제로 변수 X가 무엇인가"에 대해 이야기할 때 이 문제를 다루고 있다는 것을 알 수 있습니다.
대부분의 학생들은 메모리 청크의 간단한 도면(일반적으로 현재 범위의 스택의 로컬 변수 섹션)을 이해할 수 있었습니다.일반적으로 다양한 위치에 명시적인 가상의 주소를 제공하는 것이 도움이 되었다.
요약하자면 포인터를 이해하려면 변수와 변수들이 현대 건축에서 실제로 무엇인지 이해해야 한다는 것입니다.
저는 참고 자료의 품질과 개인적으로 가르치는 사람들을 비난합니다.C의 대부분의 개념(특히 포인터)은 제대로 가르쳐지지 않았습니다.나는 계속 내 자신의 C책을 쓰겠다고 협박하지만('세상에 필요한 마지막 것은 C 프로그래밍 언어에 관한 또 다른 책이다'라는 제목) 그럴 시간도 인내심도 없다.그래서 나는 여기서 돌아다니며 사람들에게 표준에서 무작위로 인용한 말을 던진다.
또한 C가 처음 설계되었을 때 일상적인 업무에서 기계 아키텍처를 피할 방법이 없다고 가정했습니다(메모리는 너무 꽉 차 있고 프로세서는 너무 느려서 무엇을 쓰는 것이 퍼포먼스에 어떤 영향을 미치는지 이해해야 했습니다).
포인터(저수준 작업의 일부 다른 측면과 함께)는 사용자가 마법을 사용할 필요가 있습니다.
대부분의 고급 프로그래머들은 마법을 좋아한다.
돌이켜보면, 내가 마침내 포인터를 이해하는데 도움을 준 것은 네 가지였다.이전에는 사용할 수 있었지만, 충분히 이해하지 못했습니다.즉, 서식을 따라가면 원하는 결과를 얻을 수 있다는 것은 알고 있었지만, 서식의 '왜'는 제대로 이해하지 못했습니다.저는 이것이 당신이 요구한 것과 정확히 다르다는 것을 알고 있지만, 저는 그것이 유용한 귀결이라고 생각합니다.
정수로 포인터를 가져와서 정수를 수정한 루틴을 작성합니다.이것은 나에게 포인터가 어떻게 작용하는지에 대한 정신적 모델을 만드는 데 필요한 형태를 주었다.
1차원 동적 메모리 할당1-D 메모리 할당을 파악하면서 포인터의 개념을 이해하게 되었습니다.
2차원 동적 메모리 할당.2D 메모리 할당을 통해 이러한 개념이 강화되었지만 포인터 자체에 스토리지가 필요하며 고려해야 한다는 것도 알게 되었습니다.
스택 변수, 글로벌 변수 및 힙 메모리의 차이.이러한 차이를 파악함으로써 포인터가 가리키는 메모리의 종류를 알 수 있었습니다.
이 항목들은 모두 낮은 수준에서 무슨 일이 일어나고 있는지 상상해야 했습니다. 즉, 내가 던질 수 있는 모든 경우를 만족시키는 정신적 모델을 구축해야 했습니다.시간과 노력이 들었지만 그럴 가치가 충분히 있었다.포인터를 이해하기 위해서는 포인터가 어떻게 동작하고 어떻게 구현되는지에 대한 멘탈 모델을 구축해야 한다고 확신합니다.
이제 원래의 질문으로 돌아가겠습니다.앞의 리스트를 보면, 원래 이해하기 어려운 항목이 몇 개 있었습니다.
- 포인터를 어떻게 그리고 왜 사용하는가.
- 어레이와 어떻게 다릅니까?
- 포인터 정보가 저장되는 위치를 이해합니다.
- 포인터가 가리키는 위치와 대상을 파악합니다.
포인터를 올바르게 이해하려면 기본 시스템의 아키텍처에 대한 지식이 필요합니다.
오늘날 많은 프로그래머들은 자신의 기계가 어떻게 작동하는지 알지 못합니다. 마치 자동차를 운전하는 법을 아는 대부분의 사람들이 엔진에 대해 아무것도 알지 못하는 것처럼 말입니다.
다음은 잠시 멈춘 포인터/배열 예입니다.2개의 어레이가 있다고 가정합니다.
uint8_t source[16] = { /* some initialization values here */ };
uint8_t destination[16];
또한 memcpy()를 사용하여 소스 수신처에서uint8_t 콘텐츠를 복사하는 것이 목표입니다.다음 중 어느 것이 이 목표를 달성하는지 추측해 보십시오.
memcpy(destination, source, sizeof(source));
memcpy(&destination, source, sizeof(source));
memcpy(&destination[0], source, sizeof(source));
memcpy(destination, &source, sizeof(source));
memcpy(&destination, &source, sizeof(source));
memcpy(&destination[0], &source, sizeof(source));
memcpy(destination, &source[0], sizeof(source));
memcpy(&destination, &source[0], sizeof(source));
memcpy(&destination[0], &source[0], sizeof(source));
정답(Spoiler Alert!)은 모두입니다."destination", "&destination" 및 "&destination[0]"은 모두 같은 값입니다."&destination"은 다른 두 가지 유형과는 다르지만 같은 값입니다."source"의 순열도 마찬가지입니다.
여담이지만 저는 개인적으로 첫 번째 버전이 더 좋아요.
먼저 C와 C++가 내가 처음 배운 프로그래밍 언어라고 말해야겠다.처음에는 C로 시작해서 학교에서 C++를 많이 하다가 다시 C로 가서 유창하게 되었습니다.
C를 배울 때 포인터에 대해 가장 먼저 혼란스러웠던 것은 다음과 같습니다.
char ch;
char str[100];
scanf("%c %s", &ch, str);
이러한 혼란은 포인터가 적절하게 나에게 소개되기 전에 OUT 인수에 대한 변수에 대한 참조를 사용하는 것에 주로 뿌리를 두고 있습니다.C for Dummies의 첫 번째 예시는 너무 단순해서 첫 번째 프로그램(아마도 이것 때문에)을 작성하지 못한 것으로 기억합니다.
이게 헷갈렸던 게 뭐냐면&ch
사실 이유는 물론 의도도 있었다str
필요없었어요
나는 그것에 익숙해진 후 동적 할당에 대해 혼란스러웠던 것을 기억한다.어떤 유형의 동적 할당 없이는 데이터에 대한 포인터가 매우 유용하지 않다는 것을 깨달았기 때문에 다음과 같은 글을 썼습니다.
char * x = NULL;
if (y) {
char z[100];
x = z;
}
동적으로 공간을 할당하려고 합니다.효과가 없었어요.잘 될지는 몰랐지만 다른 방법은 몰랐어요.
나중에 알게 된 것은malloc
그리고.new
하지만 제게는 마법의 기억 생성기처럼 보였어요나는 그들이 어떻게 작동할지 전혀 몰랐다.
얼마 후 저는 재귀에 대해 다시 배우고 있었습니다(이전에는 혼자 배웠지만 지금은 수업 중이었습니다). 저는 후드 아래에서 어떻게 작동하는지 물었습니다. 개별 변수는 어디에 저장되었습니까?교수님이 "더미 위에"라고 말씀하셨고 많은 것이 분명해졌습니다.이전에 이 용어를 들어본 적이 있고 소프트웨어 스택을 구현한 적이 있습니다.나는 오래 전에 다른 사람들이 "더미"를 언급하는 것을 들었지만, 그것을 잊고 있었다.
이 무렵 C에서 다차원 어레이를 사용하면 매우 혼란스러울 수 있다는 것도 깨달았습니다.어떻게 동작하는지는 알고 있었지만, 너무 쉽게 얽혀 버려서, 틈틈이 사용하려고 했습니다.여기서의 문제는 통사적(특히 함수에 전달하거나 함수에서 반환)이었던 것 같습니다.
앞으로 1~2년 동안 학교에서 C++를 쓰면서 데이터 구조에 포인터를 사용한 경험이 많아졌습니다.여기에 새로운 문제가 생겼습니다. 포인터가 뒤섞여 있습니다.여러 레벨의 포인터를 가지고 있다(예:node ***ptr;
( )에 걸려 넘어뜨립니다.포인터를 몇 번 잘못 참조했다가 결국엔 몇 번을 더 찾아냈는지 알아내는 데 의존할 거야*
나는 시행착오를 거쳐서 필요했다.
어느 순간 프로그램 힙이 어떻게 작동하는지를 알게 되었다(어느 정도, 하지만 더 이상 밤잠을 설치지 않을 정도로 훌륭하다).포인터 앞에 몇 바이트를 보면malloc
특정 시스템이 반환되면 실제로 할당된 데이터의 양을 확인할 수 있습니다.나는 그 암호가malloc
OS에 메모리를 증설하도록 요구할 수 있습니다.이 메모리는 실행 파일의 일부가 아닙니다.어떻게 해야 하는지 잘 알고 있다.malloc
일은 정말 유용하다.
그 후 얼마 지나지 않아 조립 수업을 들었는데, 대부분의 프로그래머들이 생각하는 것만큼 포인터에 대해 많이 가르쳐 주지 않았습니다.제 코드가 어떤 어셈블리로 번역될지 좀 더 생각해 볼 수 있었습니다.나는 항상 효율적인 코드를 작성하려고 노력했지만, 이제 더 나은 방법을 알게 되었다.
나는 또한 혀 짧은 글을 써야 하는 수업을 몇 개 들었다.lisp를 쓸 때 나는 C에서처럼 효율에 대해 신경쓰지 않았다.이 코드가 컴파일되면 어떤 형태로 변환될지 전혀 몰랐지만, 많은 로컬 이름 있는 기호(변수)를 사용하면 훨씬 쉬워진다는 것을 알고 있었습니다.어느 시점에서 AVL 트리 회전 코드를 약간의 lisp로 작성했는데 포인터의 문제로 인해 C++로 작성하는데 매우 어려움을 겪었습니다.과도한 로컬 변수에 대한 거부감이 C++에서 다른 프로그램을 작성하는 데 걸림돌이 되고 있음을 깨달았습니다.
컴파일러 수업도 들었어요.이 수업에서 고급 자료로 넘어가 정적 단일 할당(SSA)과 비활성 변수에 대해 배웠습니다. 이것은 괜찮은 컴파일러라면 더 이상 사용되지 않는 변수를 적절히 다룰 수 있다는 것을 가르쳐준다는 것 외에는 그리 중요하지 않습니다.올바른 유형과 좋은 이름을 가진 더 많은 변수(포인터 포함)가 머릿속을 정리하는 데 도움이 된다는 것을 이미 알고 있었지만, 이제는 효율성 때문에 그것들을 피하는 것이 덜 최적화된 교수들이 말하는 것보다 더 어리석다는 것도 알았습니다.
그래서 저는 프로그램의 메모리 레이아웃에 대해 잘 아는 것이 큰 도움이 되었습니다.상징적으로나 하드웨어적으로나 내 코드가 무엇을 의미하는지 생각하면 도움이 됩니다.올바른 유형의 로컬 포인터를 사용하면 많은 도움이 됩니다.다음과 같은 코드를 작성하는 경우가 많습니다.
int foo(struct frog * f, int x, int y) {
struct leg * g = f->left_leg;
struct toe * t = g->big_toe;
process(t);
포인터 타입을 잘못 사용하면 컴파일러 오류로 문제가 무엇인지 알 수 있습니다.사용했을 경우:
int foo(struct frog * f, int x, int y) {
process(f->left_leg->big_toe);
포인터 타입이 틀리면 컴파일러 오류는 훨씬 더 어려워집니다.저는 제 좌절의 시행착오 변화에 의지하여 상황을 더 악화시키고 싶을 것입니다.
C에서 텔레포니 프로그램을 작업하는 '시점'이 있었습니다.클래식 C만 이해하는 프로토콜 분석기를 사용하여 AXE10 교환 에뮬레이터를 작성해야 했습니다.모든 것은 힌트를 아는 것에 달려 있었다.저는 그들 없이 코드를 작성하려고 했지만(이봐, 저는 "점수 전"이었기 때문에 좀 느슨해졌어요) 완전히 실패했어요.
그것들을 이해하기 위한 열쇠는 &(주소) 오퍼레이터였습니다.내가 그것을 이해했을 때&i
"i의 주소"를 의미하고, 그 다음에 이해한다는 것을 의미합니다.*i
'i가 지적한 주소의 내용'이 조금 뒤에 나왔어요.코드를 쓰거나 읽을 때마다 "&"의 의미와 "*"의 의미를 반복했고 결국 직관적으로 사용하게 되었습니다.
아쉽게도 VB에 들어가 자바에 들어가 포인터 지식은 예전만큼 날카롭지 않지만 '포스트 포인트'가 되어 다행입니다.단, **p를 이해해야 하는 라이브러리를 사용하라고 하지 마십시오.
포인터의 가장 큰 어려움은 적어도 제가 C로 시작하지 않았다는 것입니다.자바부터 시작했어요.포인터에 대한 모든 개념은 내가 C를 알게 될 것으로 예상되는 대학에서 몇 개의 수업을 듣기 전까지는 정말 낯설었다.그래서 저는 C의 기본과 기본적 의미에서의 포인터 사용법을 독학했습니다.그래도 C코드를 읽을 때마다 포인터 구문을 찾아봐야 합니다.
그래서 나의 매우 제한된 경험(실제 세계 1년+대학 4년)에서는 교실 이외의 다른 곳에서 그것을 실제로 사용해 본 적이 없기 때문에 포인터가 나를 혼란스럽게 한다.그리고 C나 C++가 아닌 JAVA로 CS를 시작하는 학생들도 공감할 수 있습니다.말씀하신 것처럼 '신석기 시대'에 포인터를 배웠고 그 이후로 계속 사용하고 있을 것입니다.새로운 사람들에게는 메모리를 할당하고 포인터 계산을 한다는 개념이 정말 생소합니다. 왜냐하면 이 언어들은 모두 그것을 추상화했기 때문입니다.
추신: Spolsky 에세이를 읽은 후, 그의 'Java Schools'에 대한 설명은 제가 코넬 대학에서 겪었던 것과 전혀 달랐습니다.저는 구조와 기능 프로그래밍(sml), 운영체제(C), 알고리즘(펜과 종이), 그리고 자바어로 가르치지 않은 다른 많은 수업을 들었습니다.그러나 인트로 클래스와 선택과목은 모두 Java로 이루어졌는데, 포인터로 해시 테이블을 구현하는 것보다 더 높은 수준의 작업을 하려고 할 때 휠을 재창조하지 않는 것이 가치가 있기 때문입니다.
비답변은 다음과 같습니다.cdecl(또는 c++decl)을 사용하여 구합니다.
eisbaw@leno:~$ cdecl explain 'int (*(*foo)(const void *))[3]'
declare foo as pointer to function (pointer to const void) returning pointer to array 3 of int
구문을 크게 변경하지 않고 코드에 차원을 추가합니다.생각해 보세요.
int a;
a = 5
변경할 수 있는 것은 단 한 가지입니다.a
쓸 수 있다a = 6
결과는 대부분의 사람들에게 명백하다.하지만 지금 생각해 보세요.
int *a;
a = &some_int;
에는 두 가지 점이 있다a
서로 다른 시기에 관련된 것: 실제 가치a
, 포인터 및 값이 포인터 뒤에 있습니다.변경할 수 있습니다.a
:
a = &some_other_int;
...그리고.some_int
여전히 같은 가치를 가진 어딘가에 있어요그러나 다음을 가리키는 항목을 변경할 수도 있습니다.
*a = 6;
개념적으로 차이가 있다a = 6
국소적인 부작용만 있고*a = 6
다른 장소의 다른 많은 것들에 영향을 미칠 수 있습니다.여기서의 요점은, 간접의 개념이 본질적으로 까다롭다는 것이 아니라, 즉석에서 로컬로 할 수 있기 때문입니다.a
또는 간접적인 것*a
그게 사람들을 혼란스럽게 할 수도 있어요
약 2년 동안 c++로 프로그래밍을 한 후 자바(5년)로 전환하여 되돌아본 적이 없습니다.그러나 최근 토종품을 사용해야 했을 때 포인터에 대해 잊어버린 것이 없고 사용하기 쉽다는 것을 (놀라움으로) 알게 되었습니다.7년 전 처음 개념을 파악했을 때와는 확연히 다른 모습이다.그럼 이해와 호감이 프로그램 성숙도의 문제라고 생각됩니다. :)
또는
포인트는 자전거를 타는 것과 같기 때문에, 일단 그것을 어떻게 사용하는지를 알게 되면, 그것을 잊을 수 없다.
전체적으로 파악이 어렵든 아니든 포인터 아이디어는 매우 교육적이며 포인터가 있는 언어로 프로그래밍을 하든 아니든 모든 프로그래머가 이해해야 한다고 생각합니다.
포인터는 방향 변경으로 인해 어렵습니다.
포인터는 객체에 대한 핸들과 객체 자체의 차이를 처리하는 방법입니다.(오케이, 꼭 물건이라고는 할 수 없지만, 무슨 말인지 알겠지만, 내 마음이 어디에 있는지 알잖아요.)
어느 시점에서는, 당신은 그 둘의 차이를 다루어야 할 것입니다.현대의 고급 언어에서는 이것이 가치별 복사와 참조별 복사 간의 구별이 됩니다.는 종종 프로그래머들 파악이 어렵지만 어느 쪽이든, 그것은 있는 개념이다.
으로 지적되어 왔다 그러나, C에 이 문제를 처리하기 위한 구문, 일관성 없는, 헷갈리기도 하고 못생겼다.만약 여러분이 정말로를 이해하려고 결국, 포인터 말이 될 것 같다때 포인터에 포인터를 다루는 그렇게 광고 nauseum에 시작하면, 그것은 정말로 나를 뿐만 아니라 다른 사람들에게 혼동을 가져옵니다.
포인터에 대해 다른 중요한 것 기억할 것은 위험한 있다.C가 마스터 프로그래머의 언어이다.그것은 뭐 하시는 건지 도대체 알고 있고 따라서 당신은 힘이 정말 일을 망칠 수 있는 것으로 가정한다.반면 프로그램의 일부 유형은 여전히 C에 적혀 있어야 할 필요가 있어, 대부분의 프로그램 및 경우에 개체 및 해당 개체의 핸들 사이의 차이를 더 나은 추상화를 제공하는 언어가 있다면, 나는 당신이 그것을 사용하지 않는다.
실제로 많은 현대 C++ 어플리케이션에서는 필요한 포인터 산술이 캡슐화되고 추상화되는 경우가 많습니다.우리는 개발자들이 포인터 계산을 여기저기서 하는 것을 원하지 않는다.우리는 가장 낮은 수준에서 포인터 계산을 수행하는 잘 테스트된 중앙 집중식 API를 원합니다.이 코드에 대한 변경은 세심한 주의를 기울여 광범위한 테스트를 실시해야 합니다.
C 포인터가 어려운 이유 중 하나는 실제로 동등하지 않은 여러 개념을 혼동하기 때문이라고 생각합니다.그러나 그것들은 모두 포인터를 사용하여 구현되기 때문에 사람들은 개념을 혼란스럽게 만드는 데 어려움을 겪을 수 있습니다.
C에서 포인터는 다음과 같은 다른 작업에 사용됩니다.
- 재귀 데이터 구조 정의
C에서는 다음과 같은 정수의 링크 리스트를 정의합니다.
struct node {
int value;
struct node* next;
}
포인터는 C에서 재귀 데이터 구조를 정의하는 유일한 방법이기 때문에 메모리주소와 같은 낮은 수준의 세부사항과는 실제로 관계가 없는 개념이기 때문입니다.포인터를 사용할 필요가 없는 Haskell에서는 다음과 같은 동등한 것을 고려하십시오.
data List = List Int List | Null
매우 간단합니다. 목록이 비어 있거나 값 및 목록의 나머지 부분으로 구성됩니다.
- 문자열 및 어레이에 걸쳐 반복
함수를 적용하는 방법은 다음과 같습니다.foo
C 문자열의 모든 문자:
char *c;
for (c = "hello, world!"; *c != '\0'; c++) { foo(c); }
포인터를 반복기로도 사용하지만 이 예에서는 이전 예와 거의 공통점이 없습니다.증분할 수 있는 반복기 작성은 재귀 데이터 구조를 정의하는 것과는 다른 개념입니다.어느 개념도 메모리 주소의 개념과 특별히 관련이 있는 것은 아닙니다.
- 다형성을 실현하다
glib에서 발견된 실제 기능 시그니처는 다음과 같습니다.
typedef struct g_list GList;
void g_list_foreach (GList *list,
void (*func)(void *data, void *user_data),
void* user_data);
와! 꽤 많은 양인데void*
s. 모든 것은 모든 것을 포함할 수 있는 목록에 반복하는 기능을 선언하고 각 멤버에게 함수를 적용하는 것입니다.비교하다map
는 Haskell로 선언되어 있습니다.
map::(a->b)->[a]->[b]
이것은 훨씬 더 간단합니다.map
를 변환하는 함수를 사용하는 함수입니다.a
에 대해서b
, 및 그것을 의 리스트에 적용합니다.a
목록을 제출하다b
s. C 함수와 마찬가지로g_list_foreach
,map
적용할 유형에 대해 자체 정의에서 아무것도 알 필요가 없습니다.
To sum up:
I think C pointers would be a lot less confusing if people first learned about recursive data structures, iterators, polymorphism, etc. as separate concepts, and then learned how pointers can be used to implement those ideas in C, rather than mashing all of these concepts together into a single subject of "pointers".
The problem I have always had (primarily self-taught) is the "when" to use a pointer. I can wrap my head around the syntax for constructing a pointer but I need to know under which circumstances a pointer should be used.
Am I the only one with this mindset? ;-)
Once upon a time... We had 8 bit microprocessors and everyone wrote in assembly. Most processors included some type of indirect addressing used for jump tables and kernels. When higher level languages came along we add a thin layer of abstraction and called them pointers. Over the years we have gotten more and more away from the hardware. This is not necessarily a bad thing. They are called higher level languages for a reason. The more I can concentrate on what I want to do instead of the details of how it is done the better.
It seems many students have a problem with the concept of indirection, especially when they meet the concept of indirection for the first time. I remember from back when I was a student that out of the +100 students of my course, only a handful of people really understood pointers.
The concept of indirection is not something that we often use in real life, and therefore it's a hard concept to grasp initially.
I have recently just had the pointer click moment, and I was surprised that I had been finding it confusing. It was more that everyone talked about it so much, that I assumed some dark magic was going on.
The way I got it was this. Imagine that all defined variables are given memory space at compile time(on the stack). If you want a program that could handle large data files such as audio or images, you wouldn't want a fixed amount of memory for these potential structures. So you wait until runtime to assign a certain amount of memory to holding this data(on the heap).
Once you have your data in memory, you don't want to be copying that data all around your memory bus every time you want to run an operation on it. Say you want to apply a filter to your image data. You have a pointer that starts at the front of the data you have assigned to the image, and a function runs across that data, changing it in place. If you didn't know what you we're doing, you would probably end up making duplicates of data, as you ran it through the operation.
At least that's the way I see it at the moment!
Speaking as a C++ newbie here:
The pointer system took a while for me to digest not necessarily because of the concept but because of the C++ syntax relative to Java. A few things I found confusing are:
(1) Variable declaration:
A a(1);
vs.
A a = A(1);
vs.
A* a = new A(1);
and apparently
A a();
is a function declaration and not a variable declaration. In other languages, there's basically just one way to declare a variable.
(2) The ampersand is used in a few different ways. If it is
int* i = &a;
then the &a is a memory address.
OTOH, if it is
void f(int &a) {}
then the &a is a passed-by-reference parameter.
Although this may seem trivial, it can be confusing for new users - I came from Java and Java's a language with a more uniform use of operators
(3) Array-pointer relationship
One thing that's a tad bit frustrating to comprehend is that a pointer
int* i
can be a pointer to an int
int *i = &n; //
or
can be an array to an int
int* i = new int[5];
And then just to make things messier, pointers and array are not interchangeable in all cases and pointers cannot be passed as array parameters.
This sums up some of the basic frustrations I had with C/C++ and its pointers, which IMO, is greatly compounded by the fact that C/C++ has all these language-specific quirks.
I personally did not understand the pointer even after my post graduation and after my first job. The only thing I was knowing is that you need it for linked list, binary trees and for passing arrays into functions. This was the situation even at my first job. Only when I started to give interviews, I understand that the pointer concept is deep and has tremendous use and potential. Then I started reading K & R and writing own test program. My whole goal was job-driven.
At this time I found that pointers are really not bad nor difficult if they are been taught in a good way. Unfortunately when I learn C in graduation, out teacher was not aware of pointer, and even the assignments were using less of pointers. In the graduate level the use of pointer is really only upto creating binary trees and linked list. This thinking that you don't need proper understanding of pointers to work with them, kill the idea of learning them.
Pointers.. hah.. all about pointer in my head is that it give a memory address where the actual values of whatever its reference.. so no magic about it.. if you learn some assembly you wouldn't have that much trouble learning how pointers works.. come on guys... even in Java everything is a reference..
The main problem people do not understand why do they need pointers. Because they are not clear about stack and heap. It is good to start from 16bit assembler for x86 with tiny memory mode. It helped many people to get idea of stack, heap and "address". And byte:) Modern programmers sometimes can't tell you how many bytes you need to address 32 bit space. How can they get idea of pointers?
Second moment is notation: you declare pointer as *, you get address as & and this is not easy to understand for some people.
And the last thing I saw was storage problem: they understand heap and stack but can't get into idea of "static".
ReferenceURL : https://stackoverflow.com/questions/4025768/what-do-people-find-difficult-about-c-pointers
'source' 카테고리의 다른 글
VueJS 구성 요소에서 getElementById를 사용하는 모듈을 가져오는 중 (0) | 2022.08.20 |
---|---|
Linux에서 GDB를 시작할 때 명령줄 인수를 전달하려면 어떻게 해야 합니까? (0) | 2022.08.20 |
모키토로 마지막 수업을 조롱하는 방법 (0) | 2022.08.20 |
부팅 시 Eclipse가 중단되는 것을 방지하려면 어떻게 해야 합니까? (0) | 2022.08.20 |
Netflow 레코드는 옥텟(jnca)을 가져올 수 없습니다. (0) | 2022.08.20 |