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에서 다차원 배열을 사용하는 것이 매우 혼란 스러울 수 있음을 깨달았습니다. 나는 그들이 어떻게 작동하는지 알았지 만, 너무 쉽게 엉 키게되어 가능할 때마다 사용하기 위해 노력하기로 결정했습니다. 나는 여기서 문제가 대부분 문법적이라고 생각합니다 (특히 함수로 전달하거나 함수에서 반환).
내년에 학교를 위해 C ++을 작성하고 있었기 때문에 데이터 구조에 대한 포인터를 사용하여 많은 경험을 얻었습니다. 여기에 포인터를 섞는 새로운 문제가 발생했습니다. 여러 수준의 포인터 (같은 것 node ***ptr;
)가 나를 위로 움직일 것입니다. 포인터를 잘못된 횟수만큼 역 참조하고 결국 *
시행 착오에 필요한 수 를 파악하는 데 의지합니다 .
어느 시점에서 나는 프로그램의 힙이 어떻게 작동했는지 배웠습니다 (다양하지만 밤에 더 이상 나를 유지할 수 없을 정도로 충분합니다). malloc
특정 시스템 에서 포인터가 반환 되기 전에 몇 바이트를 보면 실제로 할당 된 데이터 양을 볼 수 있습니다. 코드가 malloc
OS에서 더 많은 메모리를 요구할 수 있으며이 메모리가 내 실행 파일의 일부가 아님을 깨달았습니다 . 작동 방식에 대한 적절한 작업 아이디어를 갖는 것이 malloc
실제로 유용합니다.
그 후 곧 어셈블리 클래스를 들었는데, 대부분의 프로그래머가 생각하는 것만 큼 포인터에 대해 가르쳐주지 않았습니다. 내 코드가 어떤 어셈블리로 변환 될지에 대해 더 많이 생각하게했습니다. 나는 항상 효율적인 코드를 작성하려고 시도했지만 이제는 더 나은 아이디어를 얻었습니다.
나는 또한 약간의 lisp 를 작성 해야하는 두 개의 수업을 들었다 . lisp를 작성할 때 C에서와 같이 효율성에 관심이 없었습니다.이 코드가 컴파일 될 때 어떤 코드로 변환 될지 거의 알지 못했지만 많은 로컬 명명 된 기호 (변수)를 사용하는 것처럼 보였습니다. 일이 훨씬 쉬워졌습니다. 어느 시점에서 나는 약간의 lisp로 AVL 트리 회전 코드를 썼습니다. 포인터 문제로 인해 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);
거기에 포인터 유형이 잘못되면 컴파일러 오류를 파악하기가 훨씬 더 어려울 것입니다. 나는 좌절의 시행 착오 변화에 의지하여 유혹을 품고 아마도 상황을 악화시킬 수있다.