사람들이 C 포인터에 대해 어려운 점은 무엇입니까? [닫은]


173

여기에 게시 된 많은 질문에서 사람들이 포인터와 포인터 산술 주위를 둘러 볼 때 약간의 근본적인 문제가 있음이 분명합니다.

왜 그런지 궁금합니다. 그들은 실제로 큰 문제를 일으킨 적이 없습니다 (신석기 시대에서 처음으로 그것들에 대해 배웠지 만). 이 질문들에 대한 더 나은 답변을 작성하기 위해, 사람들이 어려워하는 것을 알고 싶습니다.

따라서 포인터로 어려움을 겪고 있거나 최근에 갑자기 "가져왔다"면 문제를 일으킨 포인터의 측면은 무엇입니까?


54
으스스하고, 과장되고, 불필요하게 논란의 여지 가 많으며, 진실로 대답합니다. 그들은 Daniel Boone이 의도 한 신과 같은 베어 메탈로 프로그래밍하는 것으로 시작했습니다!
dmckee --- 전 변조 고양이 새끼

3
최선의 노력에도 불구하고 토론으로 발전 하기 때문에 프로그래머가 더 나을 것 입니다 .
dmckee --- 전 변조 고양이 새끼

이것은 논쟁적이고 주관적이며 N 개의 결과를 생성합니다. 아마도 프로그래머에게는 도움이 될 것이지만 베타 버전이 아니기 때문에 아직 이러한 질문을 마이그레이션하지 않고 있습니다.
Sam Saffron

2
@Sam Saffron : 일반적으로 이것이 프로그래머가 아니라는 데 동의합니다 .SE 유형의 질문이지만, 사람들이 "쉽게 생각합니다"라고 표시하고 "포인터가 표시되는 것을 싫어합니다"라고 스팸으로 표시하면 나쁘지 않습니다. 그들은.
jkerian

3
". 손가락에 집중하지 마십시오 그것은 멀리 달을 가리키는 손가락처럼 또는 당신은 하늘의 영광이 모든 것을 놓칠 것"--Bruce 리 : 누군가가이 업을 가지고있다
MU가 너무 짧으면

답변:


86

나는 사람들이 그들의 답변에 너무 깊이 가고 있다고 생각합니다. 스케줄링, 실제 CPU 작동 또는 어셈블리 레벨 메모리 관리에 대한 이해는 실제로 필요하지 않습니다.

가르 칠 때 학생들의 이해에서 다음과 같은 구멍이 가장 일반적인 문제의 원인이라는 것을 알았습니다.

  1. 힙 대 스택 스토리지. 일반적인 의미 에서조차 얼마나 많은 사람들이 이것을 이해하지 못하는지는 놀랍습니다.
  2. 스택 프레임. 로컬 변수에 대한 스택의 전용 섹션에 대한 일반적인 개념과 그것이 스택이라는 이유와 함께 반환 위치 숨김, 예외 처리기 세부 정보 및 이전 레지스터와 같은 세부 정보는 누군가가 시도 할 때까지 안전하게 남겨 둘 수 있습니다 컴파일러를 빌드하십시오.
  3. "메모리는 메모리는 메모리이다"캐스팅은 단지 어떤 버전의 연산자 나 컴파일러가 특정 메모리 청크를 위해 얼마나 많은 공간을 제공하는지 변경합니다. 사람들이 "(기본) 변수 X가 실제로 무엇인가"에 대해 이야기 할 때이 문제를 다루고 있다는 것을 알고 있습니다 .

대부분의 학생들은 일반적으로 현재 범위에서 스택의 로컬 변수 섹션 인 메모리 덩어리의 단순화 된 그림을 이해할 수있었습니다. 일반적으로 다양한 위치에 허구의 주소를 명시 적으로 알려주는 것이 도움이되었습니다.

요약하면, 포인터를 이해하려면 변수와 현대 아키텍처의 변수를 이해해야한다고 말하고 있습니다.


13
IMHO, 스택 및 힙 이해는 낮은 수준의 CPU 세부 정보만큼 불필요합니다. 스택 및 힙은 구현 세부 정보입니다. ISO C 사양에는 "stack"이라는 단어가 하나도없고 K & R도 없습니다.
sigjuice

4
@sigjuice : 귀하의 이의 제기는 질문과 답변 모두의 요점을 놓칩니다. A) K & R C는 시대를 초월한 것입니다. B) ISO C는 포인터가있는 유일한 언어가 아닙니다. 제 1과 2 지점은 비 C 기반 언어에 대해 개발되었습니다. C) 언어의 아키텍쳐 중 95 %가 힙을 사용합니다 / stack 시스템에서 예외에 대해 설명하는 것이 일반적입니다. D) 문제의 핵심은 "ISO C를 어떻게 설명해야합니까?"가 아니라 "포인터를 이해하지 못하는 이유"였습니다.
jkerian

9
@John Marchetti : 훨씬 더 ... 질문에 "포인터 관련 문제에 대한 근본적인 문제"라는 점을 감안할 때, 포인터 관련 질문을하는 사람들에게 "You don ' "정말로 대답해야합니다. 분명히 그들은 동의하지 않습니다. :)
jkerian

3
@jkerian 구식 일 수도 있지만 포인터를 설명하는 3 ~ 4 페이지의 K & R은 구현 세부 사항이 없어도 그렇게합니다. 구현 세부 사항에 대한 지식은 여러 가지 이유로 유용하지만 IMHO는 언어의 주요 구성을 이해하기위한 전제 조건이 아니어야합니다.
sigjuice

3
"일반적으로 여러 장소에 허구의 주소를 밝히는 것이 도움이되었습니다." -> +1
fredoverflow

146

내가 그들과 처음 작업을 시작했을 때 가장 큰 문제는 구문이었습니다.

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에서 아무것도한지 오래되었습니다. 나는 그것을 놓쳤지만 약간의 질량 학자입니다)


21
첫 번째에 대한 귀하의 의견 >> * ip = 4; // ip의 값을 '4'<<로 설정합니다. ip가 가리키는 값을 '4'로 >> // 설정해야합니다.
aaaa bbbb

8
서로 너무 많은 유형을 쌓아 두는 것은 어떤 언어로도 나쁜 생각입니다. "foo (& (*** ipppArr));"라고 쓸 수 있습니다. C에서는 이상하지만 "std :: map <std :: pair <int, int>, std :: pair <std :: vector <int>, std :: tuple <int, double, std :: list C ++의 <int >>>> "도 매우 복잡합니다. 그렇다고 C ++의 C 또는 STL 컨테이너에있는 포인터가 복잡하다는 의미는 아닙니다. 코드 리더가 이해하기 쉽도록 더 나은 유형 정의를 사용해야 함을 의미합니다.
Patrick

20
나는 구문에 대한 오해 가 가장 많이 투표 된 답변 이라고 진심으로 믿을 수 없다 . 포인터에 대한 가장 쉬운 부분입니다.
제이슨

4
이 답을 읽음에도 종이 한 장을 가져와 그림을 그리려는 유혹을 받았습니다. C에서는 항상 그림을 그렸습니다.
Michael Easter

19
@Jason 대다수의 사람들이 어렵게 생각하는 것 외에 어떤 객관적인 난이도가 있습니까?
Rupert Madden-Abbott

52

포인터를 제대로 이해하려면 기본 시스템의 아키텍처에 대한 지식이 필요합니다.

오늘날 많은 프로그래머들은 자동차 운전 방법을 알고있는 대부분의 사람들이 엔진에 대해 아무것도 모르는 것처럼 기계 작동 방식을 모릅니다.


18
@ dmckee : 글쎄, 내가 틀렸어? 몇 명의 Java 프로그래머가 segfault를 처리 할 수 ​​있습니까?
Robert Harvey

5
segfaults는 스틱 시프트와 관련이 있습니까?
Tom Anderson

6
@ 로버트 : 그것은 진정한 보완책으로 사용되었습니다. 이것은 사람들의 감정을 해치지 않으면 서 논의하기 어려운 주제입니다. 그리고 나는 내 의견이 당신이 피할 수 있다고 생각한 갈등을 불러 일으킨 것을 두려워합니다. Mea cupla.
dmckee --- 전

30
동의하지 않습니다. 포인터를 얻기 위해 기본 아키텍처를 이해할 필요는 없습니다 (어쨌든 추상화 임).
Jason Jason

11
@Jason : C에서 포인터는 기본적으로 메모리 주소입니다. 기계 아키텍처를 이해하지 않고 안전하게 작업하는 것은 불가능합니다. en.wikipedia.org/wiki/Pointer_(computing)boredzo.org/pointers/#definition
Robert Harvey

42

포인터를 다룰 때 혼란스러워하는 사람들은 두 캠프 중 하나에 널리 퍼져 있습니다. 나는 둘 다에 있었다.

array[]군중

이것은 포인터 표기법에서 배열 표기법으로 변환하는 방법을 알지 못하는 군중입니다 (또는 심지어 관련성이 있음조차 알지 못합니다). 다음은 배열 요소에 액세스하는 네 가지 방법입니다.

  1. 배열 이름을 가진 배열 표기법 (인덱싱)
  2. 포인터 이름으로 배열 표기법 (인덱싱)
  3. 포인터 이름이있는 포인터 표기법 (*)
  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 pointerpointer 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()있으며 대부분의 사람들은 때때로 그러한 잔학 행위를 저지르는 죄책감을 느끼지 만 코드는 좋지 않으며 코드를 유지할 수는 없습니다. .

글쎄,이 대답은 내가 기대했던 것보다 길다.


5
나는 당신이 C 질문에 C ++ 답변을 주었을 것입니다 ... 적어도 두 번째 부분.
detly

포인터에 대한 포인터는 C에도 적용됩니다 .p
David Titarenco

1
응? 배열을 전달할 때 포인터에 대한 포인터 만 볼 수 있습니다. 두 번째 예제는 실제로 가장 괜찮은 C 코드에는 적용되지 않습니다. 또한 C를 C ++의 엉망으로 드래그하고 있습니다 .C에는 참조가 없습니다.
new123456

많은 합법적 인 경우 포인터에 대한 포인터를 다루어야합니다. 예를 들어, 포인터를 반환하는 함수를 가리키는 함수 포인터를 다룰 때. 또 다른 예 : 가변 개수의 다른 구조체를 담을 수있는 구조체 그리고 더 많은 ...
David Titarenco

2
"참조에 대한 포인터는 C에서 불법입니다"- "없음"과 유사합니다. :)
Kos

29

나는 참고 자료의 질과 개인적으로 가르치는 사람들을 비난합니다. C의 대부분의 개념 ( 특히 포인터)은 잘못 가르쳐 져 있습니다. 나는 내 자신의 C 책 ( The Last Thing The World Needs Is The C Book Language on The C Programming Language ) 을 작성하겠다고 계속 위협하고 있지만 그렇게 할 시간이나 인내심은 없습니다. 그래서 나는 여기서 놀고 사람들에게 표준에서 무작위 인용을 던졌습니다.

또한 C가 처음 설계되었을 때 일상적인 작업에서 피할 수있는 방법이 없었기 때문에 머신 아키텍처를 매우 세부적인 수준으로 이해 했다고 가정 했습니다 (메모리가 너무 빡빡하고 프로세서가 너무 느 렸습니다) 작성한 내용이 성능에 어떤 영향을 미치는지 이해해야했습니다.)


3
예. 'int foo = 5; int * pfoo = & foo; 그것이 얼마나 유용한 지보십시오? 알았어, 움직여 .. '나는 내 자신의 이중 연결리스트 라이브러리를 작성할 때까지 포인터를 사용하지 않았다.
John Lopez

2
+1. 나는 CS100 학생들을지도하는데 사용하며, 많은 문제는 이해하기 쉬운 방법으로 포인터를 넘어서 해결되었습니다.
benzado

1
역사적 맥락에서 +1. 그 시간이 지나서 시작한 적이 없었습니다.
Lumi

26

Joel Spolsky의 사이트 인 The Perils of JavaSchools 에 포인터가 어렵다는 개념을 뒷받침하는 훌륭한 기사가 있습니다 .

[면책 조항-본인은 Java-hater 자체가 아닙니다 .]


2
@Jason-그건 사실이지만 논쟁을 부정하지 않습니다.
Steve Townsend

4
Spolsky는 JavaSchools가 사람들이 포인터를 찾기 어려운 이유라고 말하지 않습니다. 그는 그들이 컴퓨터 과학 학위를 가진 포인터 불완전한 사람들을 초래한다고 말합니다.
benzado

1
@benzado-공정한 요점- '포인터가 어렵다는 개념을 뒷받침하는 훌륭한 기사'를 읽으면 간단한 게시물이 개선됩니다. 이 기사가 의미하는 바는 " '좋은 학교에서 CS 학위를받는 것'이 개발자만큼이나 성공을 예측하는 것은 아니지만"포인터를 이해하는 "(그리고 재귀)은 여전히 ​​그렇다는 것입니다.
Steve Townsend

1
@Steve Townsend : Mr. Spolsky의 주장이 빠졌다고 생각합니다.
Jason Jason

2
@Steve Townsend : Spolsky 씨는 Java 학교가 포인터와 재귀를 모르는 차세대 프로그래머를 키우고 있다고 주장합니다. Java 학교의 보급으로 인해 포인터가 어려운 것은 아닙니다. 당신이 "이것이 왜 어려운지에 대한 훌륭한 기사가있다"고 말한 것과 같이, 당신이 후자의 해석을 가지고있는 것 같습니다. 내가 틀렸다면 용서 해줘
Jason Jason

24

"아래에있는"지식에 근거하지 않으면 대부분의 것들을 이해하기가 더 어렵습니다. CS를 가르쳤을 때 학생들이 매우 간단한 "머신", 즉 십진 레지스터와 십진 주소로 구성된 메모리를 가진 십진 opcode를 가진 시뮬레이션 된 십진 컴퓨터를 프로그래밍하는 것이 훨씬 쉬워졌습니다. 예를 들어, 총계를 얻기 위해 일련의 숫자를 추가하는 매우 짧은 프로그램을 넣었습니다. 그런 다음 그들은 무슨 일이 일어나고 있는지보기 위해 한 발짝 씩 걸었습니다. "enter"키를 누르고 "빠른"실행을 볼 수 있습니다.

나는 거의 모든 사람들이 왜 그렇게 기본을 얻는 것이 유용한 지 궁금합니다. 프로그래밍하는 법을 모른다는 것이 어떤 것인지 잊어 버립니다. 이러한 장난감 컴퓨터를 가지고 노는 것은 계산이 단계별 프로세스라는 아이디어, 소수의 기본 프리미티브를 사용하여 프로그램을 구축하는 아이디어, 메모리 개념과 같이 프로그래밍 할 수없는 개념을 제자리에 둡니다. 변수의 주소 또는 이름이 포함 된 숫자와 구별되는 숫자가 저장되는 위치로 변수. 프로그램에 들어가는 시간과 프로그램이 실행되는 시간에는 차이가 있습니다. 매우 간단한 프로그램, 루프 및 서브 루틴, 배열, 순차적 I / O, 포인터 및 데이터 구조와 같은 일련의 "스피드 범프"를 교차하는 것으로 프로그래밍하는 것을 배우는 것을 좋아합니다.

마지막으로 C에 도달하면 포인터가 혼란스럽고 K & R이이를 잘 설명해주었습니다. 내가 C에서 배운 방식은 오른쪽에서 왼쪽으로 읽는 방법을 아는 것입니다. int *p내 머리에서 볼 때처럼 ""를 p가리 킵니다 int. C는 어셈블리 언어에서 한 단계 올라간 것으로 발명되었으며 그것이 제가 좋아하는 것입니다. "접지"에 가깝습니다. 다른 접지와 마찬가지로 포인터는 접지가 없으면 이해하기 어렵습니다.


1
이것을 배우는 좋은 방법은 8 비트 마이크로 컨트롤러를 프로그래밍하는 것입니다. 이해하기 쉽습니다. Atmel AVR 컨트롤러를 사용하십시오. 그들은 심지어 gcc에 의해 지원됩니다.
Xenu

@ Xenu : 동의합니다. 나를 위해 그것은 Intel 8008과 8051이었다 :-)
Mike Dunlavey

나를 위해 그것은 어두운 안개 속에서 MIT의 맞춤형 8 비트 컴퓨터 ( "아마도")였습니다.
QuantumMechanic

마이크-학생들에게 CARDIAC을
가져와야합니다.

1
@Quantum : CARDIAC- 좋은 점은 듣지 못했습니다. "아마도"-Sussman (et al) 사람들이 Mead-Conway 책을 읽고 자신의 LSI 칩을 볼 수 있었을 때였 을까요? 그것은 내 시간이 조금 지난 후였습니다.
Mike Dunlavey

17

K & R의 설명을 읽을 때까지 포인터를 얻지 못했습니다. 그 시점까지는 포인터가 의미가 없었습니다. 나는 사람들이 "포인터를 배우지 말고 혼란 스러우며 머리를 아프게하고 동맥류를 줄 것"이라고 말한 많은 것들을 읽었습니다. .

그렇지 않으면 주로 내가 생각한 것, 왜 지구상에서 가치를 얻기 위해 후프를 통과 해야하는 변수를 원할 것이고, 물건을 할당하려면 값을 얻으려면 이상한 일을해야했습니다. 그들에게. 변수의 요점은 가치를 저장하는 것이므로 누군가가 변수를 복잡하게 만들고 싶었던 이유는 저를 넘어서는 것이라고 생각했습니다. "그래서 포인터를 사용하면 *값을 얻기 위해 연산자 를 사용해야 합니까 ??? 어떤 종류의 구피 변수입니까?" 나는 생각했다. 무의미한 말장난

그것이 복잡한 이유는 포인터가 무언가 에 대한 주소 라는 것을 이해하지 못했기 때문 입니다. 주소, 다른 주소가 포함 된 주소 및 유용한 작업을 수행하기 위해 해당 주소를 조작 할 수 있다고 설명하면 혼란을 해결할 수 있다고 생각합니다.

포인터를 사용하여 PC의 포트에 액세스 / 수정하고, 다른 메모리 위치를 처리하기 위해 포인터 산술을 사용하고, 인수를 수정 한 더 복잡한 C 코드를 살펴보면 포인터가 무의미하다는 생각이 들었습니다.


4
임베디드 응용 프로그램과 같이 (RAM, ROM, CPU)로 작업 할 리소스가 제한되어 있으면 포인터가 훨씬 더 의미가 있습니다.
Nick T

Nick의 의견에 +1-특히 구조체 전달에 대해.
new123456

12

다음은 일시 중지 한 포인터 / 배열 예제입니다. 두 개의 배열이 있다고 가정하십시오.

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!)입니다. "대상", "& 대상"및 "& 대상 [0]"은 모두 같은 값입니다. "& destination"은 다른 두 가지와 다른 유형 이지만 여전히 같은 값입니다. "소스"의 순열도 마찬가지입니다.

옆으로, 나는 개인적으로 첫 번째 버전을 선호합니다.


나는 첫 번째 버전을 선호합니다 (문구가 적음).
sigjuice

++ 그래서 내가 할,하지만 당신은 정말 조심해야하는 sizeof(source)경우 때문에, source포인터입니다, sizeof당신이 원하는 것을하지 않습니다. 나는 때때로 (항상 그런 것은 아님) sizeof(source[0]) * number_of_elements_of_source단지 그 버그에서 멀리 떨어져 있기 위해 글을 쓴다 .
Mike Dunlavey

destination, & destination, & destination [0]은 (는) 전혀 같지 않지만 memcpy에서 사용될 때 각각 다른 메커니즘을 통해 동일한 void *로 변환됩니다. 그러나 sizeof의 인수로 사용하면 두 가지 다른 결과를 얻을 수 있으며 세 가지 다른 결과가 가능합니다.
gnasher729

운영자 주소가 필요하다고 생각 했습니까?
MarcusJ

7

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;
}

공간을 동적으로 할당하려고합니다. 작동하지 않았다. 나는 그것이 효과가 있을지 확신하지 못했지만 그것이 어떻게 다른지 알지 못했습니다.

나중에 mallocnew에 대해 배웠지 만 실제로는 마술 같은 메모리 생성기처럼 보였습니다. 나는 그들이 어떻게 작동하는지 전혀 몰랐다.

얼마 후 나는 재귀를 다시 배우게되었고 (이전에 스스로 배웠지 만 지금은 수업 중이었다), 나는 그것이 개별 변수가 저장된 곳에서 어떻게 작동하는지 물었다. 교수님은 "스택에서"라고 말했고 많은 것들이 나에게 분명해졌습니다. 나는 전에 그 용어를 들었고 전에 소프트웨어 스택을 구현했다. 다른 사람들이 오래 전에 "스택"을 언급한다고 들었지만 잊어 버렸습니다.

이 시점에서 나는 C에서 다차원 배열을 사용하는 것이 매우 혼란 스러울 수 있음을 깨달았습니다. 나는 그들이 어떻게 작동하는지 알았지 만, 너무 쉽게 엉 키게되어 가능할 때마다 사용하기 위해 노력하기로 결정했습니다. 나는 여기서 문제가 대부분 문법적이라고 생각합니다 (특히 함수로 전달하거나 함수에서 반환).

내년에 학교를 위해 C ++을 작성하고 있었기 때문에 데이터 구조에 대한 포인터를 사용하여 많은 경험을 얻었습니다. 여기에 포인터를 섞는 새로운 문제가 발생했습니다. 여러 수준의 포인터 (같은 것 node ***ptr;)가 나를 위로 움직일 것입니다. 포인터를 잘못된 횟수만큼 역 참조하고 결국 *시행 착오에 필요한 수 를 파악하는 데 의지합니다 .

어느 시점에서 나는 프로그램의 힙이 어떻게 작동했는지 배웠습니다 (다양하지만 밤에 더 이상 나를 유지할 수 없을 정도로 충분합니다). malloc특정 시스템 에서 포인터가 반환 되기 전에 몇 바이트를 보면 실제로 할당 된 데이터 양을 볼 수 있습니다. 코드가 mallocOS에서 더 많은 메모리를 요구할 수 있으며이 메모리가 내 실행 파일의 일부가 아님을 깨달았습니다 . 작동 방식에 대한 적절한 작업 아이디어를 갖는 것이 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);

거기에 포인터 유형이 잘못되면 컴파일러 오류를 파악하기가 훨씬 더 어려울 것입니다. 나는 좌절의 시행 착오 변화에 의지하여 유혹을 품고 아마도 상황을 악화시킬 수있다.


1
+1. 철저하고 통찰력있는. 나는 scanf를 잊어 버렸지 만 이제는 당신이 그것을 가져 왔으므로 같은 혼란을 겪었던 것을 기억합니다.
조 화이트

6

되돌아 보면 포인터를 마침내 이해하는 데 실제로 도움이 된 네 가지가있었습니다. 그 전에는 사용할 수 있었지만 완전히 이해하지 못했습니다. 즉, 양식을 따랐는지 알고 원하는 결과를 얻을 수는 있지만 양식의 '이유'를 완전히 이해하지 못했습니다. 나는 이것이 당신이 요구 한 것이 아니라는 것을 알고 있지만 그것이 유용한 목록이라고 생각합니다.

  1. 정수에 대한 포인터를 가져와 정수를 수정하는 루틴 작성 이것은 포인터가 작동하는 방식에 대한 정신적 모델을 구축하는 데 필요한 양식을 제공했습니다.

  2. 1 차원 동적 메모리 할당. 1D 메모리 할당을 알아 내면 포인터의 개념을 이해할 수있었습니다.

  3. 2 차원 동적 메모리 할당. 2-D 메모리 할당을 알아 내면 그 개념이 강화되었지만 포인터 자체에는 저장이 필요하며 고려해야한다는 점을 가르쳐주었습니다.

  4. 스택 변수, 전역 변수 및 힙 메모리의 차이점 이러한 차이점을 파악하면 포인터가 가리키는 메모리 유형을 알 수 있습니다.

이러한 각 항목은 저수준에서 진행되고있는 일을 상상해야했습니다. 제가 던질 것으로 생각할 수있는 모든 사례를 만족시키는 정신적 모델을 구축했습니다. 시간과 노력이 들었지만 그만한 가치가있었습니다. 포인터를 이해하려면 포인터가 작동하는 방식과 구현 방식에 대한 정신적 모델을 구축해야한다고 확신합니다.

이제 원래 질문으로 돌아갑니다. 이전 목록을 바탕으로 원래 파악하기 어려웠던 몇 가지 항목이있었습니다.

  1. 포인터를 사용하는 방법과 이유.
  2. 그것들은 배열과 어떻게 다르고 유사합니까?
  3. 포인터 정보가 저장된 위치 이해
  4. 포인터가 가리키는 곳과 위치를 이해합니다.

이봐 요, 당신은 당신이 당신의 대답에서 묘사 한 것과 비슷한 방식으로 배울 수있는 기사 / 책 / 그림 / 낙서 / 무엇을 가리킬 수 있습니까? 나는 이것이 기본적으로 무엇이든 잘 배울 때 갈 길이라고 굳게 믿고 있습니다. 깊은 이해와 좋은 정신 모델
Alexander Starbuck

1
@AlexStarbuck-이것이 플립 팬트를 의미하는 것은 아니지만 과학적 방법은 훌륭한 도구입니다. 특정 시나리오에서 발생할 수있는 일에 대한 그림을 직접 그려보십시오. 그것을 테스트하기 위해 무언가를 프로그래밍하고 얻은 것을 분석하십시오. 예상 한 것과 일치 했습니까? 그렇지 않다면 어디가 다른지 식별 하시겠습니까? 필요에 따라 반복하여 이해도 및 정신 모델을 테스트하기 위해 복잡성을 점차적으로 증가시킵니다.
Sparky

6

C의 일부 전화 프로그램에서 "포인터 모멘트"를 작업했습니다. 고전적인 C 만 이해하는 프로토콜 분석기를 사용하여 AXE10 교환 에뮬레이터를 작성해야했습니다. 모든 것이 포인터를 아는 데 달려있었습니다. 나는 그것들없이 내 코드를 작성하려고 시도했다 (이봐, 나는 "프리 포인터"라고 느슨하게 잘라 버렸다).

나를 이해하기위한 열쇠는 & (주소) 연산자였습니다. 내가 &i" 나의 주소" 를 의미한다는 것을 이해하면*i 가리키는 주소의 내용" 의미한다는 것을 하면 조금 후에 나왔습니다. 코드를 쓰거나 읽을 때마다 항상 "&"의 의미와 "*"의 의미를 반복하여 결국 직관적으로 사용하게되었습니다.

부끄러운 일로, 나는 VB와 Java로 강요되어 포인터 지식이 예전만큼 예리하지는 않지만 "포스트 포인터"라는 것이 기쁘다. 그래도 * * p 를 이해해야하는 라이브러리를 사용하도록 요청하지 마십시오 .


&i주소이고 *i내용 이라면 무엇 i입니까?
Thomas Ahle

2
나는 i의 사용법을 과부하시키고 있습니다. 임의의 변수 i의 경우, & i는 "주소"i를 의미하고, i 자체는 "& i의 내용"을 의미하고 * i는 "& i의 내용을 주소로 처리하고 해당 주소로 이동하여 다시 전달합니다. 내용".
게리 로우

5

적어도 나에게 포인터의 주된 어려움은 C로 시작하지 않았다는 것입니다. Java로 시작했습니다. 포인터에 대한 모든 개념은 C를 알아야 할 것으로 예상되는 대학의 두 수업 때까지 실제로 외국이었습니다. 그래서 저는 C의 기초와 포인터를 기본적으로 사용하는 방법을 스스로에게 가르쳐주었습니다. 그럼에도 불구하고 C 코드를 읽을 때마다 포인터 구문을 찾아야합니다.

따라서 매우 제한된 경험 (실제 1 년 + 대학에서 4 년)에서, 교실 환경 이외의 다른 곳에서 사용하지 않았기 때문에 포인터가 혼란 스럽습니다. 그리고 C 또는 C ++ 대신 JAVA를 사용하여 CS를 시작하는 학생들과 공감할 수 있습니다. 당신이 말했듯이, 당신은 '신석기 시대'의 포인터를 배웠고 그 이후로 그것을 사용하고 있었을 것입니다. 우리는 새로운 사람들에게 메모리를 할당하고 포인터 산술을하는 개념은 외국어를 추상화했기 때문에 실제로 외국어입니다.

추신 : Spolsky 에세이를 읽은 후 'JavaSchools'에 대한 그의 설명은 내가 Cornell 대학 ('05 -'09)에서 겪었던 것과 같지 않았습니다. 구조와 기능적 프로그래밍 (sml), 운영 체제 (C), 알고리즘 (펜 및 종이) 및 Java로 가르쳐지지 않은 다른 클래스를 사용했습니다. 그러나 포인터가있는 해시 테이블을 구현하는 것보다 더 높은 수준의 무언가를하려고 할 때 바퀴를 재발 명하지 않는 가치가 있기 때문에 모든 소개 클래스와 선택 과목은 모두 Java로 수행되었습니다.


4
솔직히 말해서, 당신이 여전히 포인터에 어려움을 겪고 있다는 것을 감안할 때, Cornell에서의 경험이 Joel의 기사와 실질적으로 모순되는지 확실하지 않습니다. 분명히 당신의 두뇌는 자바 마인드로 연결되어 그의 지적을 할 수 있습니다.
jkerian

5
왓? Java (또는 C # 또는 Python 또는 수십 개의 다른 언어)의 참조는 산술이없는 포인터 일뿐입니다. 포인터를 이해한다는 것은 논란 void foo(Clazz obj) { obj = new Clazz(); }이없는 이유를 이해하는 것을 의미 void bar(Clazz obj) { obj.quux = new Quux(); }합니다.

1
Java의 참조가 무엇인지 알고 있지만 Java에서 리플렉션을 수행하거나 CI에서 의미있는 것을 쓰라고 요청하면 그것을 막을 수는 없습니다. 매번 처음 배우는 것과 같은 많은 연구가 필요합니다.
shoebox639

1
C에 유창하지 않고 C의 운영 체제 클래스를 어떻게 통과 했습니까? 공격 의도가 없으며 단순한 운영 체제를 처음부터 거의 개발해야한다는 것을 기억합니다. 나는 ... 사용 포인터를 천 번이 있어야합니다
중력

5

대답이 아닙니다. 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

4

구문을 크게 변경하지 않고 코드에 추가 차원을 추가합니다. 이것에 대해 생각하다:

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... 그 사람을 혼란스럽게 무슨 수 있습니다.


4

나는 2 년 동안 C ++로 프로그래밍 한 다음 Java (5 년)로 변환했으며 결코 뒤돌아 보지 않았습니다. 그러나 최근에 네이티브 기능을 사용해야 할 때 포인터에 대해 아무것도 잊어 버리지 않았으며 사용하기 쉽다는 것을 알게되었습니다. 이것은 7 년 전 처음으로 개념을 파악하려고했던 것과는 대조적입니다. 따라서 이해와 선호는 프로그래밍 성숙도의 문제라고 생각합니다. :)

또는

포인터는 자전거를 타는 것과 같습니다. 일단 자전거를 다루는 방법을 알아 낸 후에는 잊지 않아도됩니다.

전체적으로, 이해하기 어려운, 전체 포인터 아이디어는 매우 교육적이며 포인터가있는 언어로 프로그래밍하는지 여부에 관계없이 모든 프로그래머가 이해해야한다고 생각합니다.


3

간접적으로 인해 포인터가 어렵습니다.


"간접 하나 개 더 수준에 의해 해결 될 수없는 컴퓨터 과학에 문제가 없음을 말한다"(먼저 말했다 아무 생각도 없지만)
전형적인 폴

그것은 잘못된 방향으로 사람들을 혼란스럽게하는 마술과 같습니다 (그러나 완전히 대단합니다)
Nick T

3

포인터 (저급 작업의 다른 측면과 함께)는 사용자가 마법을 빼앗아 야합니다.

대부분의 고급 프로그래머는 마술을 좋아합니다.


3

포인터는 객체 핸들과 객체 자체의 차이를 처리하는 방법입니다. (확인해야 할 것은 아니지만 내 마음의 위치뿐만 아니라 내가 의미하는 바를 알고 있음)

어느 시점에서, 당신은 아마 둘 사이의 차이를 다루어야 할 것입니다. 현대의 고급 언어에서는 이것이 값별 복사와 참조 별 복사의 구별이됩니다. 어느 쪽이든 프로그래머가 이해하기 어려운 개념입니다.

그러나 지적 된 바와 같이, C에서이 문제를 처리하기위한 구문은 추악하고 일관성이없고 혼란 스럽다. 결국 실제로 이해하려고하면 포인터가 의미가 있습니다. 그러나 포인터에 대한 포인터를 다루기 시작하면 광고 구역에서 다른 사람들뿐만 아니라 나에게도 혼란스러워집니다.

포인터에 대해 기억해야 할 또 다른 중요한 점은 포인터가 위험하다는 것입니다. C는 마스터 프로그래머의 언어입니다. 그것은 당신이하고있는 지옥을 알고 있다고 가정하여 실제로 물건을 엉망으로 만들 수있는 힘을줍니다. 일부 유형의 프로그램은 여전히 ​​C로 작성해야하지만 대부분의 프로그램은 그렇지 않으며 객체와 핸들의 차이점에 대한 더 나은 추상화를 제공하는 언어가 있으면 사용하는 것이 좋습니다.

실제로 많은 현대 C ++ 응용 프로그램에서 필요한 포인터 산술이 캡슐화되고 추상화되는 경우가 종종 있습니다. 우리는 개발자들이 모든 곳에서 포인터 산술을하는 것을 원하지 않습니다. 우리는 가장 낮은 수준에서 포인터 산술을 수행하는 잘 테스트 된 중앙 집중식 API를 원합니다. 이 코드를 변경하려면 세심한주의와 광범위한 테스트를 거쳐야합니다.


3

C 포인터가 어려운 이유 중 하나는 실제로는 그렇지 않은 여러 개념을 혼동하기 때문입니다. 그러나 모두 포인터를 사용하여 구현 되었기 때문에 사람들은 개념을 분리하는 데 어려움을 겪을 수 있습니다.

C에서 포인터는 다른 것들을 발생시키는 데 사용됩니다.

  • 재귀 데이터 구조 정의

C에서는 다음과 같이 연결된 정수 목록을 정의합니다.

struct node {
  int value;
  struct node* next;
}

이 개념은 메모리 주소와 같은 하위 수준의 세부 사항과 관련이없는 C에서 재귀 데이터 구조를 정의하는 유일한 방법이기 때문에 포인터 만 있습니다. 포인터를 사용할 필요가없는 Haskell에서 다음을 고려하십시오.

data List = List Int List | Null

매우 간단합니다. 목록이 비어 있거나 목록과 나머지 목록으로 구성됩니다.

  • 문자열과 배열을 반복

fooC에서 문자열의 모든 문자에 함수 를 적용하는 방법은 다음과 같습니다 .

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*. 그리고 그것은 모든 종류의 것을 포함 할 수있는 목록을 반복하여 각 멤버에 함수를 적용하는 함수를 선언하는 것입니다. mapHaskell에서 어떻게 선언 되는지 비교하십시오 :

map::(a->b)->[a]->[b]

그것은 훨씬 더 간단합니다은 : map를 변환하는 기능을한다 함수 aA를을 b, 그리고 목록에 적용 a'의 목록을 산출하기의 bs'을 (를). C 함수에서와 마찬가지로 g_list_foreach, map적용 할 유형에 대한 자체 정의에서 아무것도 알 필요가 없습니다.

요약하면 :

사람들이 재귀 적 데이터 구조, 반복자, 다형성 등에 대해 별도의 개념으로 처음 알게 된 다음 C를 사용하여 포인터를 C 에서 아이디어를 구현하는 데 사용할 수있는 방법 을 배웠다면 C 포인터가 훨씬 덜 혼란 스럽다고 생각 합니다. "포인터"의 단일 주제로 함께 개념.


c != NULL"Hello world"예제에서 오용 ... 의미 *c != '\0'합니다.
Olaf Seibert

2

기계 수준, 아마도 기계 코드, 어셈블리 및 RAM에서 항목 및 데이터 구조를 나타내는 방법을 소개하는 견고한 기초가 필요하다고 생각합니다. 시간이 좀 걸리고 숙제 나 문제 해결 연습과 생각이 필요합니다.

그러나 사람이 처음에 높은 수준의 언어를 알고 있다면 (무슨 일이 아닙니다. 목수는 도끼를 사용하고, 원자를 쪼개 야하는 사람은 다른 것을 사용합니다. 우리는 목수 인, 원자를 연구하는 사람들이 필요합니다.) 고급 언어를 아는 사람에게는 포인터에 대한 2 분의 소개가 주어지며 포인터 산술, 포인터에 대한 포인터, 가변 크기 문자열에 대한 포인터 배열 및 문자 배열 등을 이해하기가 어렵습니다. 저수준의 견고한 기초는 많은 도움이 될 수 있습니다.


2
그로 킹 포인터는 기계 코드 또는 어셈블리에 대한 이해가 필요하지 않습니다.
제이슨

필요합니다. 그러나 집회를 이해하는 사람들은 필요한 정신 연결을 이미 대부분 (모두는 아님) 만들었 기 때문에 훨씬 더 쉽게 포인터를 찾을 수 있습니다.
cHao

2

내가 항상 겪었던 문제 (주로 스스로 가르치는)는 포인터를 사용하는 "언제"입니다. 포인터를 구성하기위한 구문으로 머리를 감쌀 수 있지만 어떤 환경에서 포인터를 사용해야하는지 알아야합니다.

이 사고 방식을 가진 유일한 사람입니까? ;-)


나는 그것을 얻는다. 내 대답은 약간을 처리합니다.
J. Polfer

2

옛날 옛적에 ... 우리는 8 비트 마이크로 프로세서를 가지고 있었고 모두가 어셈블리에 썼습니다. 대부분의 프로세서에는 점프 테이블 및 커널에 사용되는 일부 유형의 간접 주소 지정이 포함되었습니다. 더 높은 수준의 언어가 나왔을 때 얇은 추상화 계층을 추가하고이를 포인터라고했습니다. 수년에 걸쳐 우리는 하드웨어에서 점점 더 멀어졌습니다. 반드시 나쁜 것은 아닙니다. 그것들을 이유로 높은 수준의 언어라고합니다. 더 나은 방법에 대한 세부 사항 대신 내가하고 싶은 일에 더 집중할 수 있습니다.


2

많은 학생들이 간접 개념, 특히 처음으로 간접 개념을 만났을 때 문제가있는 것 같습니다. 나는 학생이었을 때부터 과정의 +100 명의 학생들 중에서 소수의 사람들 만이 실제로 포인터를 이해했음을 기억합니다.

간접의 개념은 우리가 실제 생활에서 자주 사용하는 것이 아니므로 처음에는 이해하기 어려운 개념입니다.


2

나는 최근에 포인터 클릭 순간을 가졌고 혼란스럽게 생각하는 것에 놀랐습니다. 모든 사람들이 그것에 대해 너무 많이 이야기했을 때 나는 어두운 마술이 진행되고 있다고 가정했습니다.

내가 얻은 방법은 이것입니다. 모든 정의 된 변수에 컴파일시 (스택에서) 메모리 공간이 주어진다고 상상해보십시오. 오디오 나 이미지와 같은 대용량 데이터 파일을 처리 할 수있는 프로그램을 원한다면 이러한 잠재적 구조에 대해 고정 된 양의 메모리를 원하지 않을 것입니다. 따라서 런타임 까지이 데이터를 힙에 보유하기 위해 특정 양의 메모리를 할당 할 때까지 기다립니다.

메모리에 데이터가 있으면 작업을 실행할 때마다 메모리 버스 주변에서 해당 데이터를 모두 복사하고 싶지 않습니다. 이미지 데이터에 필터를 적용한다고 가정 해보십시오. 이미지에 할당 한 데이터의 앞쪽에서 시작하는 포인터가 있으며 해당 데이터에서 함수가 실행되어 데이터가 변경됩니다. 우리가하고있는 일을 모른다면 작업을 수행하면서 데이터를 복제 할 수 있습니다.

적어도 그것이 내가 지금 보는 방식입니다!


나중에 생각할 때 메모리 제한 장치와 같이 이미지 / 오디오 / 비디오를 고정하기 위해 고정 된 양의 메모리를 정의 할 수 있지만 메모리 솔루션 안팎의 스트리밍을 처리해야합니다.
Chris Barry

1

C ++ 초보자로 말하기 :

포인터 시스템은 개념 때문이 아니라 Java와 관련된 C ++ 구문 때문에 소화하는 데 시간이 걸렸습니다. 혼란 스러웠던 몇 가지 사항은 다음과 같습니다.

(1) 변수 선언 :

A a(1);

vs.

A a = A(1);

vs.

A* a = new A(1); 

그리고 분명히

A a(); 

변수 선언이 아닌 함수 선언입니다. 다른 언어에서는 기본적으로 변수를 선언하는 한 가지 방법이 있습니다.

(2) 앰퍼샌드는 몇 가지 다른 방식으로 사용됩니다. 만약 그렇다면

int* i = &a;

& a는 메모리 주소입니다.

OTOH라면

void f(int &a) {}

& a는 참조에 의해 전달되는 매개 변수입니다.

이것은 사소한 것처럼 보이지만 새로운 사용자에게는 혼란 스러울 수 있습니다. 자바와 Java의 언어는 연산자를보다 균일하게 사용하여 유래했습니다.

(3) 배열 포인터 관계

이해하기에 약간 실망스러운 것은 포인터라는 것입니다.

int* i

int에 대한 포인터가 될 수 있습니다

int *i = &n; // 

또는

int에 대한 배열이 될 수 있습니다

int* i = new int[5];

그런 다음 더 복잡하게 만들기 위해 포인터와 배열을 서로 바꿔 사용할 수 없으며 포인터를 배열 매개 변수로 전달할 수 없습니다.

이것은 C / C ++에서 가지고있는 몇 가지 기본적인 좌절과 IMO 인 포인터가 C / C ++에 이러한 모든 언어 별 특징이 있다는 사실로 인해 크게 어려워집니다.


C ++ 2011은 변수 선언에 관한 한 상당히 개선되었습니다.
gnasher729

0

나는 졸업 후와 첫 직장 후에도 개인적으로 포인터를 이해하지 못했습니다. 내가 아는 유일한 것은 링크 된 목록, 이진 트리 및 배열을 함수에 전달하는 데 필요하다는 것입니다. 이것은 첫 직장에서도 상황이었습니다. 인터뷰를 시작했을 때만 포인터 개념이 깊고 엄청난 사용과 잠재력을 가지고 있음을 이해합니다. 그런 다음 K & R을 읽고 자체 테스트 프로그램을 작성하기 시작했습니다. 나의 전체 목표는 직무 중심이었습니다.
이 시점에서 나는 좋은 방법으로 가르쳤다면 포인터가 실제로 나쁘거나 어렵지 않다는 것을 알았습니다. 불행히도 졸업식에서 C를 배우면 교사는 포인터를 알지 못하고 과제조차도 포인터를 덜 사용했습니다. 대학원 수준에서 포인터를 사용하는 것은 실제로 이진 트리와 링크 된 목록을 만드는 것에 만 달려 있습니다. 포인터를 사용하기 위해 포인터를 제대로 이해하지 않아도된다는 생각은 학습의 아이디어를 없애줍니다.


0

포인터 .. 아 .. 내 머리 속의 포인터에 관한 모든 것은 그것이 참조의 실제 값이있는 메모리 주소를 제공한다는 것입니다. 그래서 그것에 대해 마술이 없습니다. 어떻게 포인터가 작동 하는가. 얘들 아 오라 ... 자바에서도 모든 것이 참조이다 ..


0

사람들이 왜 포인터가 필요한지 이해하지 못하는 주요 문제. 스택과 힙에 대해 명확하지 않기 때문입니다. 작은 메모리 모드의 x86 용 16 비트 어셈블러에서 시작하는 것이 좋습니다. 많은 사람들이 스택, 힙 및 "주소"에 대한 아이디어를 얻는 데 도움이되었습니다. 그리고 바이트 :) 현대 프로그래머는 때때로 32 비트 공간을 처리하는 데 필요한 바이트 수를 말할 수 없습니다. 그들은 어떻게 포인터에 대한 아이디어를 얻을 수 있습니까?

두 번째 순간은 표기법입니다. 포인터를 *로 선언하면 주소가 &로 표시되며 일부 사람들에게는 이해하기 쉽지 않습니다.

그리고 마지막으로 본 것은 스토리지 문제였습니다. 힙과 스택을 이해하지만 "정적"에 대한 아이디어를 얻을 수는 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.