초보자에게 C 포인터 (선언 대 단항 연산자)를 설명하는 방법은 무엇입니까?


141

나는 C 프로그래밍 초보자에게 포인터를 설명하는 데 최근에 기쁨을 느꼈으 며 다음과 같은 어려움에 부딪쳤다. 포인터를 사용하는 방법을 이미 알고 있다면 전혀 문제가되지 않을 수 있지만 다음 예제를 분명하게 살펴보십시오.

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

절대 초보자에게는 출력이 놀랍습니다. 2 행에서 방금 * bar를 & foo로 선언했지만 4 행에서 * bar는 실제로 & foo 대신 foo입니다!

혼란은 * 기호의 모호성에서 비롯됩니다. 2 행에서 포인터를 선언하는 데 사용됩니다. 4 행에서 포인터가 가리키는 값을 가져 오는 단항 연산자로 사용됩니다. 다른 두 가지가 맞습니까?

그러나이 "설명"은 초보자에게 전혀 도움이되지 않습니다. 미묘한 차이를 지적하여 새로운 개념을 소개합니다. 이것을 가르치는 올바른 방법이 될 수 없습니다.

Kernighan과 Ritchie는 어떻게 설명했습니까?

단항 연산자 *는 간접 또는 역 참조 연산자입니다. 포인터에 적용되면 포인터가 가리키는 객체에 액세스합니다. […]

포인터 ip의 선언은 int *ip니모닉으로 만들어졌습니다. 그것은 표현 *ip이 정수 라고 말합니다 . 변수 선언 구문은 변수가 나타날 수있는 표현식 구문과 유사합니다 .

int *ip"" *ip를 반환하는 것처럼 읽을 int까요? 그런데 왜 선언 후 과제가 그 패턴을 따르지 않습니까? 초보자가 변수를 초기화하려면 어떻게해야합니까? int *ip = 1(읽기 : *ip는를 반환 int하고 intis는 1) 예상대로 작동하지 않습니다. 개념적 모델은 일관된 것처럼 보이지 않습니다. 여기에 뭔가 빠졌습니까?


편집 : 여기에 답변요약 하려고했습니다 .


15
가장 좋은 설명은 종이에 물건을 그리고 화살표로 연결하는 것입니다;)
Maroun

16
포인터 구문을 설명해야 할 때 항상 *선언에서 "포인터 선언"을 의미하는 토큰, 표현식에서 역 참조 연산자이며이 두 가지가 동일한 기호를 갖는 다른 일을 나타낸다 는 사실을 항상 주장했습니다. (곱셈 연산자와 동일-동일한 기호, 다른 의미). 혼란 스럽지만 실제 상황과 다른 것이 더 나빠질 것입니다.
Matteo Italia

40
별을 쓰면 int* bar별이 실제로 식별자의 일부가 아닌 유형의 일부라는 것이 더 분명해집니다. 물론 이것은 당신과 같은 직관적이지 않은 것들과 다른 문제를 낳습니다 int* a, b.
Niklas B.

9
나는 항상 K & R 설명이 어리 석고 불필요하다고 생각했습니다. 언어는 서로 다른 두 가지 요소에 대해 동일한 기호를 사용하므로 처리해야합니다. *문맥에 따라 두 가지 다른 의미를 가질 수 있습니다. 같은 글자가 단어에 따라 다르게 발음 될 수있는 것처럼 많은 언어를 배우는 것을 어렵게 만듭니다. 모든 단일 개념 / 작업에 고유 한 기호가있는 경우 훨씬 더 큰 키보드가 필요하므로 기호가 적절할 때 기호가 재활용됩니다.
Art

8
나는 C에게 다른 사람들에게 가르 칠 때 같은 문제를 여러 번 겪었고, 내 경험상, 여기에서 대부분의 사람들이 제안한 방식으로 해결할 수 있습니다. 먼저 C 구문이없는 포인터의 개념을 설명하십시오. 그런 다음 구문을 가르치고 유형 ( int* p) 의 일부로 별표를 강조 하면서 포인터가 포함될 때 같은 줄에 여러 선언을 사용하지 않도록 학생 에게 경고하십시오 . 학생이 포인터의 개념을 완전히 이해했으면 int *pis 구문이 동일 하다는 것을 학생에게 설명하고 여러 선언으로 문제를 설명하십시오.
Theodoros Chatzigiannakis

답변:


43

학생들이 *다른 상황 에서 상징 의 의미를 이해 하려면 먼저 상황이 실제로 다르다는 것을 이해해야합니다. 일단 문맥이 다르다는 것을 이해하고 나면 (즉, 과제의 왼쪽과 일반 표현의 차이) 차이가 무엇인지 이해하기에는 너무 많은인지 적 도약이 아닙니다.

먼저 변수 선언에 연산자를 포함 할 수 없다고 설명하십시오 ( -또는+ 변수 선언에 기호 단순히 오류가 발생 함 ). 그런 다음 표현식 (예 : 할당의 오른쪽)에 연산자가 포함될 수 있음을 보여줍니다. 학생이 표현식과 변수 선언이 완전히 다른 상황이라는 것을 이해하도록하십시오.

문맥이 다르다는 것을 이해하면 *기호가 변수 식별자 앞에 변수 선언에 있을 때 '이 변수를 포인터로 선언'을 의미 한다고 설명 할 수 있습니다 . 그런 다음 표현식에서 단항 연산자로 사용될 때 *기호는 '역 참조 연산자'이며 이전 의미가 아니라 '주소의 값'을 의미 한다고 설명 할 수 있습니다 .

학생을 진정으로 확신시키기 위해 C의 제작자는 역 참조 연산자를 의미하는 기호를 사용할 수 있었지만 (즉, @대신 사용할 수 있음 ) 어떤 이유로 든 디자인 사용을 결정했다고 설명합니다 *.

대체로 상황이 다르다는 것을 설명 할 방법이 없습니다. 학생이 상황이 다르다는 것을 이해하지 못하면 그 *상징이 다른 것을 의미 하는 이유를 이해할 수 없습니다 .


80

속기 이유 :

int *bar = &foo;

귀하의 예에서 혼란 스러울 수있는 것은 다음과 동등한 것으로 잘못 읽는 것이 쉽다는 것입니다.

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

그것이 실제로 의미하는 경우 :

int *bar;
bar = &foo;

이렇게 변수 선언과 할당이 분리 된 상태에서 이렇게 혼동 될 가능성이 없으며 K & R 견적에 설명 된 사용 ↔ 선언 병렬 처리가 완벽하게 작동합니다.

  • 첫 번째 라인은 변수를 선언 bar하도록, *bar이다 int.

  • 두번째 라인의 어드레스 할당 foobar만들기 *bar(AN int)에 대한 별칭 foo(도를 int).

초보자에게 C 포인터 구문을 도입 할 때는 처음에 이러한 스타일의 포인터 선언을 지정에서 분리하고, 포인터의 기본 개념이 일단 사용되면 혼동 가능성에 대한 적절한 경고와 함께 결합 된 속기 구문 만 소개하는 것이 도움이 될 수 있습니다. C는 충분히 내재화되었습니다.


4
나는 유혹을 받는다 typedef. typedef int *p_int;수단 형식의 변수는 p_int속성 갖는다 *p_int이다 int. 그럼 우리는 p_int bar = &foo;. 초기화되지 않은 데이터를 만들고 나중에 기본 습관의 문제로 데이터를 할당하도록 권장하는 것은 나쁜 생각처럼 보입니다.
Yakk-Adam Nevraumont 2014

6
이것은 단지 뇌 손상을 입힌 C 선언 스타일입니다. 포인터에만 국한되지 않습니다. 고려 int a[2] = {47,11};하십시오. (존재하지 않는) 요소의 초기화가 아닙니다 a[2].
Marc van Leeuwen

5
@MarcvanLeeuwen 뇌 손상에 동의하십시오. 이상적으로 *는 변수에 바인딩되지 않은 형식의 일부 여야하며 int* foo_ptr, bar_ptr두 개의 포인터를 선언 하기 위해 작성할 수 있습니다 . 그러나 실제로 포인터와 정수를 선언합니다.
Barmar

1
"단기"선언 / 할당에 관한 것이 아닙니다. 포인터를 함수 인수로 사용하려는 순간 전체 문제가 다시 발생합니다.
armin

30

선언의 부족

선언과 초기화의 차이점을 아는 것이 좋습니다. 변수를 타입으로 선언하고 값으로 초기화합니다. 동시에 둘 다하면 정의라고합니다.

1. int a; a = 42;

int a;
a = 42;

우리는 선언int 이름 . 그런 다음 값을 지정하여 초기화합니다 42.

2. int a = 42;

우리는 선언int이름 하고 그것에게 그것은으로 초기화되는 값 (42)을 제공합니다 42. 정의.

3. a = 43;

우리가 변수를 사용할 때 우리는 그 변수를 조작 한다고 말합니다 . a = 43할당 작업입니다. 변수 43에 숫자 43을 할당합니다.

말함으로써

int *bar;

bar 를 int에 대한 포인터로 선언 합니다 . 말함으로써

int *bar = &foo;

bar 를 선언 하고 foo 주소로 초기화합니다 .

bar 를 초기화 한 후에는 동일한 연산자 인 별표를 사용하여 foo 값에 액세스하고 작동 할 수 있습니다 . 연산자가 없으면 포인터가 가리키는 주소에 액세스하여 작동합니다.

그 외에도 나는 그림을 말하게했다.

무슨 일이 일어나고 있는지에 대한 간단한 ASCIIMATION. (여기서 일시 중지하려는 플레이어 버전 )

          ASCIIMATION


22

두 번째 진술 int *bar = &foo;은 다음과 같이 메모리에서 그림으로 볼 수 있습니다.

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

이제 주소 가 포함 된 bar유형의 포인터 입니다 . 단항 연산자 를 사용하면 pointer를 사용하여 'foo'에 포함 된 값을 검색하는 것을 연기합니다 . int&foo*bar

편집 : 초보자와의 접근 방식 memory address은 변수의 예 를 설명하는 것 입니다.

Memory Address:모든 변수에는 OS에서 제공 한 것과 관련된 주소가 있습니다. 에서는 int a;, &a변수의 어드레스이다 a.

다음과 C같이 기본 변수 유형을 계속 설명하십시오 .

Types of variables: 변수는 각 유형의 값을 보유 할 수 있지만 주소는 보유 할 수 없습니다.

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: 위에서 말한 것처럼 예를 들어

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

변수 는 값을 가질 수는 있지만 주소를 지정할 수 b = a없으므로 할당 할 수는 없지만 포인터 가 필요합니다 .b = &ab

Pointer or Pointer variables :변수에 주소가 포함되어 있으면 포인터 변수라고합니다. *선언에서 포인터임을 알리려면 사용하십시오 .

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable

3
문제는 int *ip"ip는 int 유형의 포인터 (*)"로 읽는 것이와 같은 것을 읽을 때 문제를 일으킨다는 것 x = (int) *ip입니다.
armin December

2
@abw 그것은 완전히 다른 것이므로 괄호입니다. 나는 사람들이 선언과 캐스팅의 차이점을 이해하는 데 어려움을 겪지 않을 것이라고 생각합니다.
bzeaman

@abw In x = (int) *ip;에서 포인터를 역 참조하여 값을 가져 오고 모든 유형에서 ip값을 캐스트합니다 . intip
Sunil Bojanapally

1
@BennoZeeman 당신이 맞습니다 : 캐스팅과 선언은 서로 다른 두 가지입니다. 나는 별표의 다른 역할을 암시하려고 노력했다 : 1st "이것은 int가 아니라 int에 대한 포인터"2 번째 "이것은 int를 제공하지만 int에 대한 포인터는 아니다".
armin

2
@abw : 가르침이 왜 어느 int* bar = &foo;하게 부하를 더 의미. 예, 단일 선언으로 여러 포인터를 선언하면 문제가 발생한다는 것을 알고 있습니다. 아뇨, 전혀 문제가되지 않습니다.
궤도에서 가벼움 레이스

17

여기의 답변과 의견을 보면 문제의 구문이 초보자에게 혼란을 줄 수 있다는 일반적인 동의가있는 것 같습니다. 그들 대부분은 다음 라인을 따라 무언가를 제안합니다.

  • 코드를 표시하기 전에 다이어그램, 스케치 또는 애니메이션을 사용하여 포인터 작동 방식을 설명하십시오.
  • 구문을 제시 할 때 별표 기호의 두 가지 역할을 설명하십시오 . 많은 튜토리얼에서 해당 부분이 누락되거나 회피됩니다. 혼란이 뒤 따릅니다 ( "초기화 포인터 선언을 선언과 나중 과제로 나눌 때 *를 제거해야합니다."– comp.lang.c FAQ ) 대체 방법을 찾고 싶었지만 이것이 다음과 같습니다. 가는 길.

차이점을 강조하기 위해 int* bar대신 쓸 수 있습니다 int *bar. 이는 K & R "선언 모방 사용"접근 방식을 따르지 않지만 Stroustrup C ++ 접근 방식을 .

*bar정수로 선언하지 않습니다 . 우리는로 선언 bar합니다 int*. 같은 줄에서 새로 생성 된 변수를 초기화하려면 bar, 아닌을 다루는 것이 분명합니다 *bar.int* bar = &foo;

단점 :

  • 다중 포인터 선언 문제 ( int* foo, barvs int *foo, *bar)에 대해 학생에게 경고해야합니다 .
  • 당신은 상처세계에 대비해야합니다 . 많은 프로그래머들은 변수 이름 옆에 별표가 표시되기를 원하며 스타일을 정당화하기 위해 많은 시간이 걸릴 것입니다. 그리고 많은 스타일 가이드가이 표기법을 명시 적으로 적용합니다 (Linux 커널 코딩 스타일, NASA C 스타일 가이드 등).

편집 : 제안 된 다른 접근 방식 은 K & R "mimic"방법을 사용하지만 "shortshort"구문을 사용하지 않는 것입니다 ( 여기 참조 ). 같은 줄에서 선언과 할당생략 하자마자 모든 것이 훨씬 더 일관성있게 보일 것입니다.

그러나 조만간 학생은 포인터를 함수 인수로 다루어야합니다. 그리고 리턴 타입으로서의 포인터. 그리고 함수를 가리키는 포인터. 당신은 사이의 차이를 설명해야합니다 int *func();int (*func)();. 조만간 문제가 발생할 것이라고 생각합니다. 아마 나중이 빠를수록 좋습니다.


16

K & R 스타일이 선호 int *p되고 Stroustrup 스타일이 선호 되는 이유가 있습니다 int* p. 둘 다 각 언어에서 유효하며 같은 의미이지만 Stroustrup이 다음과 같이 말합니다.

"int * p;"중에서 선택 그리고 "int * p;" 옳고 그름에 관한 것이 아니라 스타일과 강조에 관한 것입니다. C는 표현을 강조했다; 선언은 종종 필요한 악에 지나지 않습니다. 반면에 C ++은 유형에 중점을 둡니다.

자, 여기서 C를 가르치려고 노력하고 있기 때문에, 당신은 그 유형보다 표현을 강조해야한다고 제안 할 것입니다. 그러나 어떤 사람들은 다른 것보다 한 가지 강조점을 더 빨리 잡을 수 있고 그것은 언어가 아닌 그들에 관한 것입니다.

따라서 어떤 사람들int* 은 a가 다른 것과 다르다는 생각으로 시작하고 int거기에서 나가는 것이 더 쉽다는 것을 알게 될 것 입니다.

누군가가 빠르게 용도가 그것을보고의 방법 grok 수없는 경우 int* bar가지고 bar있는 int가 아닌 것 같은,하지만에 대한 포인터를 int, 그들은 신속하게이 볼 수 *bar있다 뭔가 일 까지를 bar, 나머지는 따를 것이다. 일단 그렇게하면 나중에 C 코더가 선호하는 이유를 나중에 설명 할 수 있습니다 int *bar.

아님 모든 사람이 먼저 개념을 이해하는 한 가지 방법이 있다면 처음에는 아무런 문제가 없었을 것이며, 한 사람에게 설명하는 가장 좋은 방법은 다른 사람에게 그것을 설명하는 가장 좋은 방법은 아닙니다.


1
나는 Stroustrup의 주장을 좋아하지만 왜 그가 또 다른 가능한 함정 인 참조를 나타 내기 위해 & 기호를 선택했는지 궁금합니다.
armin

1
내가 생각 @abw 그는 우리가 할 수있는 경우에 대칭보고 int* p = &a우리가 할 수 있습니다 int* r = *p. 나는 그가 C ++의 디자인과 진화 (Design and Evolution of C ++) 에서 그것을 덮었다 고 확신 하지만, 그것을 읽은 지 오랜 시간이 걸렸으며 어리석게도 내 사본을 누군가에게 빌려주었습니다.
Jon Hanna

3
당신이 의미하는 것 같아요 int& r = *p. 그리고 나는 차용인이 여전히 책을 소화하려고 노력하고 있습니다.
armin December

@ abw, 그렇습니다. 정확히 의미했습니다. 주석의 오타는 컴파일 오류를 발생시키지 않습니다. 이 책은 실제로 활발히 읽었습니다.
Jon Hanna

4
나는 C의 이상 (로 널리 확장) 파스칼의 구문을 선호하는 이유 중 하나는 즉 Var A, B: ^Integer;유형 "정수 포인터가"모두 적용 명확하게 A하고 B. K&R스타일을 사용하는 int *a, *b것도 가능합니다. 그러나 선언처럼은 int* a,b;, 그러나, 그러나 같은 외모 ab모두가 선언되고 같이 int*하지만, 실제로는 선언 aint로서 int*그리고 bint로서 int.
supercat

9

tl; dr :

Q : 초보자에게 C 포인터 (선언 대 단항 연산자)를 설명하는 방법은 무엇입니까?

A :하지 마십시오. 초보자를위한 포인터를 설명하고 C 구문으로 포인터 개념을 나타내는 방법을 보여줍니다.


나는 C 프로그래밍 초보자에게 포인터를 설명하는 데 최근에 기쁨을 느꼈으 며 다음과 같은 어려움에 부딪쳤다.

C 구문 IMO는 끔찍하지는 않지만 훌륭하지도 않습니다. 포인터를 이미 이해하고 있거나 배우는 데 도움이된다면 큰 방해가되지 않습니다.

따라서 포인터를 설명하는 것으로 시작하여 실제로 이해해야합니다.

  • 상자와 화살표 다이어그램으로 설명하십시오. 16 진수 주소가 없으면 관련이없는 경우 다른 상자 또는 null 기호를 가리키는 화살표를 표시하십시오.

  • 의사 코드로 설명하십시오 : foo의 주소bar에 저장된 값을 쓰십시오 .

  • 그런 다음, 초보자가 포인터가 무엇인지, 그리고 왜, 어떻게 사용하는지 이해하면; 그런 다음 C 구문에 대한 매핑을 보여줍니다.

나는 K & R 텍스트가 개념적 모델을 제공하지 않는 이유는 그들이 이미 포인터를 이해 했기 때문이며 아마도 당시의 다른 모든 유능한 프로그래머도 그랬다고 가정했을 것입니다. 니모닉은 잘 이해 된 개념에서 구문으로의 매핑을 상기시켜줍니다.


과연; 이론부터 시작하여 구문은 나중에 나옵니다 (중요하지는 않습니다). 메모리 사용 이론은 언어에 의존하지 않습니다. 이 상자 및 화살표 모델은 모든 프로그래밍 언어의 작업에 도움이됩니다.
oɔɯǝɹ

몇 가지 예를 보려면 여기를 참조하십시오 (Google도 도움이 됨) eskimo.com/~scs/cclass/notes/sx10a.html
oɔɯǝɹ

7

이 문제는 C를 배우기 시작할 때 다소 혼란 스럽다.

시작하는 데 도움이되는 기본 원칙은 다음과 같습니다.

  1. C에는 몇 가지 기본 유형 만 있습니다.

    • char: 1 바이트 크기의 정수 값.

    • short: 2 바이트 크기의 정수 값.

    • long: 크기가 4 바이트 인 정수 값.

    • long long: 8 바이트 크기의 정수 값.

    • float: 크기가 4 바이트 인 정수가 아닌 값.

    • double: 8 바이트 크기의 정수가 아닌 값.

    각 유형의 크기는 일반적으로 표준이 아닌 컴파일러에 의해 정의됩니다 .

    정수 유형 short, longlong long일반적으로 다음에 있습니다 int.

    그러나 필수는 아니며 int. 없이 사용할 수 있습니다 .

    또는 state 만 할 수는 int있지만 컴파일러마다 다르게 해석 될 수 있습니다.

    이를 요약하면 다음과 같습니다.

    • short와 동일 short int하지만 반드시 동일 하지는 않습니다 int.

    • long와 동일 long int하지만 반드시 동일 하지는 않습니다 int.

    • long long와 동일 long long int하지만 반드시 동일 하지는 않습니다 int.

    • 주어진 컴파일러에서 int중 하나입니다 short int또는 long intlong long int.

  2. 어떤 유형의 변수를 선언하면 해당 변수를 가리키는 다른 변수를 선언 할 수도 있습니다.

    예를 들면 다음과 같습니다.

    int a;

    int* b = &a;

    본질적으로 각 기본 유형마다 해당 포인터 유형이 있습니다.

    예를 들면 다음 short과 같습니다 short*.

    두 가지 변수 "모양은"이있다 b (아마도 대부분의 초보자를 혼란 무엇의 그) :

    • b유형의 변수로 간주 할 수 있습니다 int*.

    • *b유형의 변수로 간주 할 수 있습니다 int.

    따라서 어떤 사람들은 선언 int* b하고 다른 사람들은 선언 int *b합니다.

    그러나 문제의 사실은이 두 선언이 동일하다는 것입니다 (공백은 의미가 없습니다).

    b정수 값에 대한 포인터로 사용 하거나*b 실제 뾰족한 정수 값으로.

    지정된 값을 얻을 수 있습니다. int c = *b .

    그리고 지정된 값을 설정 (쓰기) 할 수 있습니다 *b = 5.

  3. 포인터는 이전에 선언 한 일부 변수의 주소뿐만 아니라 모든 메모리 주소를 가리킬 수 있습니다. 그러나 지정된 메모리 주소에있는 값을 가져 오거나 설정하려면 포인터를 사용할 때주의해야합니다.

    예를 들면 다음과 같습니다.

    int* a = (int*)0x8000000;

    여기에는 a메모리 주소 0x8000000을 가리키는 변수가 있습니다 .

    이 메모리 주소가 프로그램의 메모리 공간 내에 매핑되지 않은 경우 *a, 메모리 액세스 위반으로 인해 사용하는 모든 읽기 또는 쓰기 작업 으로 인해 프로그램이 중단 될 수 있습니다.

    의 값을 안전하게 변경할 수 a있지만의 값을 변경하는 데 매우주의해야합니다 *a.

  4. 유형 void*은 사용할 수있는 해당 "값 유형"이 없기 때문에 예외적입니다 (즉, 선언 할 수 없음 void a). 이 유형은 해당 주소에있는 데이터 유형을 지정하지 않고 메모리 주소에 대한 일반적인 포인터로만 사용됩니다.


7

아마도 조금 더 단계별로 살펴보면 더 쉬울 것입니다.

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

각 라인에 출력이있을 것으로 예상 한 것을 알려주고 프로그램을 실행하고 무엇이 나오는지 확인하게하십시오. 그들의 질문을 설명하십시오 (알몸 버전은 확실히 몇 가지를 요구하지만 나중에 스타일, 엄격 성 및 이식성에 대해 걱정할 수 있습니다). 그런 다음, 그들의 생각이 지나친 생각에서 흐릿 해 지거나 점심 식사 후 좀비가되기 전에 값을 취하는 함수와 포인터를 취하는 동일한 함수를 작성하십시오.

내 경험상 "이런 식으로 인쇄되는 이유는 무엇입니까?" 험프 (Hump)를 누른 다음 , 수업이 이해하기 쉬울뿐 아니라 스틱 잉 / 배열 처리와 같은 기본 K & R 자료에 대한 전제로 실습을 통해 기능 매개 변수에 유용한 이유 즉시 보여줍니다.

다음 단계에 설명을 얻는 것입니다 당신이 방법 i[0]에 관한 것이다 &i. 그들이 그렇게 할 수 있다면, 그들은 그것을 잊지 않을 것이고, 당신은 구조체에 대해 조금이라도 미리 이야기 할 수 있습니다.

상자와 화살표에 대한 위의 권장 사항도 좋지만 메모리가 어떻게 작동하는지에 대한 완전한 토론으로 빠져 나올 수 있습니다. 이는 어떤 시점에서 발생해야하지만 즉각적인 시점에서 산만해질 수 있습니다. : C에서 포인터 표기법을 해석하는 방법


이것은 좋은 운동입니다. 그러나 제가 제기하고자하는 문제는 학생들이 구축 한 정신 모델에 영향을 줄 수있는 구체적인 구문 문제입니다. 이것을 고려하십시오 : int foo = 1;. 이제 이것은 정상입니다 : int *bar; *bar = foo;. 이것은 정상이 아닙니다 :int *bar = foo;
armin

1
@abw 이해가되는 유일한 것은 학생들이 스스로 이야기하는 것입니다. 그것은 "하나를보고,하고, 가르치고"를 의미합니다. 정글에서 볼 수있는 구문이나 스타일 (예 : 이전 레포지토리)을 보호하거나 예측할 수 없으므로 기본 개념이 스타일과 독립적으로 이해 될 수있는 충분한 순열을 보여야합니다. 그런 다음 왜 특정한 스타일이 정해 졌는지를 가르치기 시작하십시오. 영어를 가르치는 것과 같은 기본 표현, 숙어, 스타일, 특정 스타일의 특정 스타일. 불행히도 쉽지 않습니다. 어쨌든 행운을 빌어 요!
zxq9

6

표현식 의 유형 *barint; 따라서 변수 (및 표현식) 의 유형은 bar입니다 int *. 변수에는 포인터 유형이 있으므로 초기화 프로그램에도 포인터 유형이 있어야합니다.

포인터 변수 초기화와 할당간에 불일치가 있습니다. 그것은 어려운 방법으로 배워야 할 것입니다.


3
여기의 답변을 보면 많은 숙련 된 프로그래머 더 이상 문제를 볼 수 없다는 느낌이 들었습니다 . 나는 이것이 "일관되지 않은 생활을 배우는"부산물이라고 생각합니다.
armin

3
@abw : 초기화 규칙은 할당 규칙과 다릅니다. 스칼라 산술 형식의 경우 차이는 무시할 수 있지만 포인터 및 집계 형식에는 중요합니다. 그것은 당신이 다른 모든 것들과 함께 설명해야 할 것입니다.
John Bode

5

개 이상에 *적용 되는 첫 번째 문서로 읽습니다 .intbar

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

2
그렇다면 왜 int* a, b그들이 생각하는 것을하지 않는지 설명해야합니다 .
Pharap

4
사실, 나는 그것이 int* a,b전혀 사용되어야 한다고 생각하지 않습니다 . 더 나은 책임, 업데이트 등을 위해서는 줄 당 하나의 변수 선언 만 있어야하며 그 이상은 없어야합니다. 컴파일러가 처리 할 수 ​​있더라도 초보자도 설명 할 수 있습니다.
Grorel

그래도 한 사람의 의견입니다. 한 줄에 하나 이상의 변수를 선언하고 매일 작업의 일부로 수행하는 프로그래머는 수백만 명의 프로그래머가 있습니다. 학생들은 다른 방법으로 일을 숨길 수 없으며, 모든 대안을 보여주고 그들이 어떤 방식으로 일을하고 싶은지를 결정하게하는 것이 좋습니다. 그들은 편안하거나 편안하지 않을 수 있습니다. 프로그래머에게는 다양성이 매우 좋은 특성입니다.
Pharap

1
@grorel에 동의합니다. *유형의 일부로 생각 하고 낙담하기 가 더 쉽습니다 int* a, b. 당신이 그것을 가리키는 것보다 *a형식 이라고 말하는 것을 선호하지 않는다면 ...intaint
Kevin Ushey

@grorel이 옳습니다 : 사용 int *a, b;해서는 안됩니다. 같은 문장에서 다른 유형의 변수 두 개를 선언하는 것은 상당히 좋지 않은 연습이며 유지 관리 문제에 대한 강력한 후보입니다. 아마도 an int*과 an의 int크기가 다르고 때로는 완전히 다른 메모리 위치에 저장되는 임베디드 필드에서 일하는 사람들에게는 다를 수 있습니다. C 언어의 많은 측면 중 하나입니다. '허용되지만해서는 안됩니다.'
Evil Dog Pie

5
int *bar = &foo;

Question 1: 무엇입니까 bar?

Ans: 포인터 변수 (유형 int)입니다. 포인터는 유효한 메모리 위치를 가리켜 야하며 나중에 *해당 위치에 저장된 값을 읽으려면 단항 연산자 를 사용하여 역 참조 (* bar)해야합니다 .

Question 2: 무엇입니까 &foo?

Ans: foo는 int유효한 메모리 위치에 저장된 타입의 변수이며 ,이 위치는 연산자로부터 얻었 &으므로 이제 우리가 가지고있는 것은 유효한 메모리 위치 &foo입니다.

그래서 두 포인터를 합치면 즉, 포인터가 필요한 것은 유효한 메모리 위치였으며 &foo그로 인해 초기화가 좋습니다.

이제 포인터 bar가 유효한 메모리 위치를 가리키고 있으며 그에 저장된 값을 참조 할 수 있습니다.*bar


5

선언과 표현에서 *의 의미가 다른 초보자를 지적해야합니다. 아시다시피, 식에서 *는 단항 연산자이며 * 선언에서 연산자가 아니며 형식과 결합하여 컴파일러가 포인터 형식임을 알리는 일종의 구문입니다. 초보자에게 "*는 다른 의미를 갖습니다. *의 의미를 이해하려면 *가 사용되는 곳을 찾아야합니다."


4

악마가 우주에 있다고 생각합니다.

나는 (초보자뿐만 아니라 나 자신도) 쓸 것이다 : int * bar = & foo; int 대신 * bar = & foo;

이것은 구문과 시맨틱 사이의 관계가 무엇인지 분명해야합니다.


4

이미 여러 가지 역할이 있다는 점에 주목했다.

초보자가 물건을 이해하는 데 도움이되는 또 다른 간단한 아이디어가 있습니다.

"="에는 여러 역할이 있다고 생각하십시오.

할당이 선언과 같은 줄에 사용될 때는 임의의 할당이 아니라 생성자 호출로 생각하십시오.

당신이 볼 때 :

int *bar = &foo;

거의 다음과 같습니다.

int *bar(&foo);

괄호는 별표보다 우선하므로 "& foo"는 "* bar"보다는 "bar"에 훨씬 더 직관적으로 기인합니다.


4

나는 며칠 전에이 질문을 보았고 Go 블로그에서 Go의 유형 선언에 대한 설명을 읽었습니다 . C 형 선언을 설명하는 것으로 시작합니다.이 답변은 이미 완료된 답변이 더 있다고 생각 하더라도이 스레드에 추가하는 데 유용한 리소스처럼 보입니다.

C는 선언 구문에 대해 독특하고 영리한 접근 방식을 취했습니다. 특별한 문법으로 타입을 설명하는 대신, 선언 될 아이템과 관련된 표현식을 작성하고 그 표현식이 어떤 타입을 가질 것인지를 명시합니다. 그러므로

int x;

x를 int로 선언합니다. 표현식 'x'는 int 유형을 갖습니다. 일반적으로 새 변수의 유형을 작성하는 방법을 알아 보려면 기본 유형으로 평가되는 해당 변수와 관련된 표현식을 작성한 다음 기본 유형을 왼쪽에, 표현식을 오른쪽에 놓으십시오.

따라서 선언

int *p;
int a[3];

'* p'는 int 유형을 가지고 있기 때문에 p는 int에 대한 포인터이고 a [3] (배열의 크기로 펀칭 된 특정 색인 값은 무시)이 있기 때문에 a는 int 배열입니다. int.

(이러한 이해를 함수 포인터 등으로 확장하는 방법을 설명합니다.)

이것은 내가 전에 생각하지 않은 방법이지만 구문의 과부하를 설명하는 매우 간단한 방법처럼 보입니다.


3

문제가 구문 인 경우 템플릿 / 사용시 동등한 코드를 표시하는 것이 도움이 될 수 있습니다.

template<typename T>
using ptr = T*;

이것은 다음과 같이 사용될 수 있습니다

ptr<int> bar = &foo;

그런 다음 일반 / C 구문을이 C ++ 전용 방법과 비교하십시오. const 포인터를 설명 할 때도 유용합니다.


2
초보자에게는 더 혼란 스러울 것입니다.
Karsten

내 생각에는 ptr의 정의를 보여주지 않을 것입니다. 포인터 선언에 사용하십시오.
MI3Guy

3

혼란의 원인은 *기호가 사용되는 사실에 따라 기호가 C에서 다른 의미를 가질 수 있다는 사실에서 발생합니다. 초보자를위한 포인터를 설명하기 위해, *다른 맥락에서 의 기호 의 의미를 설명해야합니다.

선언에서

int *bar = &foo;  

*심볼은 간접 연산자하지 . 대신 bar컴파일러에게 bar에 대한 포인터int 임을 알리는 유형을 지정하는 데 도움이됩니다 . 한편, 명령문에 *표시 될 때 기호 ( 단항 연산자 로 사용되는 경우 )는 간접 처리를 수행합니다. 따라서 진술

*bar = &foo;

자체가 아닌 foo객체에 주소를 할당하기 때문에 잘못되었습니다 .barbar


3

"int * 막대로 작성하면 별이 실제로 식별자의 일부가 아닌 유형의 일부라는 것이 더 분명해집니다." 그래서 그렇습니다. 그리고 Type과 비슷하지만 하나의 포인터 이름에만 해당됩니다.

"물론 이것은 int * a, b와 같은 직관적이지 않은 것들에 대해 다른 문제를 야기합니다."


2

여기서는 인간의 논리가 아닌 컴파일러 논리를 사용하고 이해하고 설명해야합니다 (나는 당신 이 인간이지만 컴퓨터를 모방해야합니다 ...).

당신이 쓸 때

int *bar = &foo;

컴파일러는

{ int * } bar = &foo;

즉, 여기에 새로운 변수가 있으며, 그 이름은 bar이고, 그 유형은 int에 대한 포인터이며, 초기 값은 &foo입니다.

그리고 당신은 추가해야합니다 다음 =위의 의미를 초기화하지 않은 허식, 다음 식에 반면 *bar = 2;있다 허식

주석 별 편집 :

주의 : 다중 선언의 경우 *다음 변수에만 관련됩니다.

int *bar = &foo, b = 2;

bar는 foo의 주소로 초기화 된 int에 대한 포인터이고 b는 2로 초기화 된 int입니다.

int *bar=&foo, **p = &bar;

bar는 여전히 int에 대한 포인터이고, p는 주소 또는 bar에 초기화 된 int에 대한 포인터에 대한 포인터입니다.


2
실제로 컴파일러는 다음과 같이 그룹화하지 않습니다 . a를 a에 int* a, b;대한 포인터로 선언하고 intb를 a로 선언 합니다 int. 이 *기호는 두 가지 고유 한 의미를 갖습니다. 선언에서 포인터 유형을 나타내고 표현식에서 단항 역 참조 연산자입니다.
tmlen

@tmlen : 내가 의미하는 것은 초기화 *에서 형식에 래칭되어 포인터가 초기화되는 반면 포인터는 영향을 받으면서 지정된 값에 영향을 미친다는 것입니다. 그러나 적어도 당신은 나에게 좋은 모자 :-) 준
서지 Ballesta

0

기본적으로 포인터는 배열 표시가 아닙니다. 초보자는 포인터가 배열처럼 보인다고 쉽게 생각합니다. 대부분의 문자열 예제는

"char * pstr"과 비슷합니다.

"char str [80]"

그러나 중요한 것은 포인터가 하위 수준의 컴파일러에서 정수로 취급됩니다.

예제를 보자 ::

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

결과는 다음과 같습니다. 0x2a6b7ed0 주소는 str []

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

기본적으로 포인터는 일종의 정수입니다. 주소 제시.


-1

ints는 float 등의 객체라고 설명합니다. 포인터는 값이 메모리의 주소를 나타내는 객체 유형이므로 포인터의 기본값은 NULL입니다.

포인터를 처음 선언 할 때는 type-pointer-name 구문을 사용하십시오. "정수 객체의 주소를 가리킬 수있는 name이라는 정수 포인터"로 읽습니다. int를 'int num1'로 선언하는 방법과 유사하게, Decleration 동안이 구문 만 사용하지만 'int num1'이 아닌 해당 변수를 사용하려는 경우 'num1'만 사용합니다.

int x = 5; // 5의 정수 객체

int * ptr; // 기본적으로 NULL 값을 갖는 정수

포인터가 객체의 주소를 가리 키도록하려면 "주소"로 읽을 수있는 '&'기호를 사용합니다.

ptr = & x; // 이제 값은 'x'의 주소입니다.

포인터는 객체의 주소 일 뿐이므로 해당 주소에 보유 된 실제 값을 얻으려면 포인터 앞에 사용될 때 "가 가리키는 주소의 값"을 의미하는 '*'기호를 사용해야합니다.

std :: cout << * ptr; // 주소에서 값을 출력

' '는 다른 유형의 객체로 다른 결과를 반환하는 '연산자'라고 간단히 설명 할 수 있습니다 . 포인터와 함께 사용될 때 ' '연산자는 더 이상 "곱하기"를 의미하지 않습니다.

변수에 이름과 값이 있고 포인터에 주소 (이름)와 값이있는 방법을 보여주는 다이어그램을 그리고 포인터 값이 int의 주소임을 보여주는 데 도움이됩니다.


-1

포인터는 주소를 저장하는 데 사용되는 변수 일뿐입니다.

컴퓨터의 메모리는 순차적으로 배열 된 바이트 (1 바이트는 8 비트로 구성)로 구성됩니다. 각 바이트에는 배열의 인덱스 또는 아래 첨자와 마찬가지로 숫자가 있으며이를 바이트의 주소라고합니다. 바이트의 주소는 0에서 시작하여 메모리 크기보다 작습니다. 예를 들어 64MB의 RAM에는 64 * 2 ^ 20 = 67108864 바이트가 있습니다. 따라서이 바이트의 주소는 0에서 67108863까지 시작합니다.

여기에 이미지 설명을 입력하십시오

변수를 선언 할 때 어떻게되는지 봅시다.

int 마크;

우리가 알고있는 것처럼 int는 4 바이트의 데이터를 차지하므로 (32 비트 컴파일러를 사용한다고 가정) 컴파일러는 메모리에서 4 연속 바이트를 예약하여 정수 값을 저장합니다. 할당 된 4 바이트 중 첫 번째 바이트의 주소를 변수 표시의 주소라고합니다. 4 연속 바이트의 주소가 5004, 5005, 5006 및 5007이라고 가정하면 변수 마크의 주소는 5004입니다. 여기에 이미지 설명을 입력하십시오

포인터 변수 선언

이미 말했듯이 포인터는 메모리 주소를 저장하는 변수입니다. 다른 변수와 마찬가지로 사용하기 전에 먼저 포인터 변수를 선언해야합니다. 다음은 포인터 변수를 선언하는 방법입니다.

통사론: data_type *pointer_name;

data_type은 포인터의 유형입니다 (포인터의 기본 유형이라고도 함). pointer_name은 유효한 C 식별자가 될 수있는 변수의 이름입니다.

몇 가지 예를 들어 보자.

int *ip;

float *fp;

int * ip는 ip가 int 유형의 변수를 가리킬 수있는 포인터 변수임을 의미합니다. 즉, 포인터 변수 ip는 int 유형의 변수 주소 만 저장할 수 있습니다. 마찬가지로 포인터 변수 fp는 float 유형의 변수 주소 만 저장할 수 있습니다. 변수 유형 (기본 유형이라고도 함) ip는 int에 대한 포인터이고 fp 유형은 float에 대한 포인터입니다. int에 대한 포인터 유형의 포인터 변수는 기호 적으로 (int *)로 표시 될 수 있습니다. 마찬가지로 float 형식 포인터의 포인터 변수는 (float *)로 나타낼 수 있습니다.

포인터 변수를 선언 한 후 다음 단계는 유효한 메모리 주소를 할당하는 것입니다. 선언 직후에는 가비지 값이 포함되어 메모리의 아무 곳이나 가리킬 수 있으므로 유효한 메모리 주소를 할당하지 않고 포인터 변수를 사용해서는 안됩니다. 할당되지 않은 포인터를 사용하면 예기치 않은 결과가 발생할 수 있습니다. 심지어 프로그램이 중단 될 수도 있습니다.

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

출처 : thecguru 는 지금까지 내가 찾은 가장 간단하지만 자세한 설명입니다.

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