포인터를 이해하는 데 장애물이 무엇이고,이를 극복하기 위해 무엇을 할 수 있습니까? [닫은]


449

C 나 C ++에있는 많은 새롭고 오래된 대학 수준의 학생들에게 포인터가 왜 혼란의 주요 요인입니까? 변수, 함수 및 수준을 넘어서 포인터가 작동하는 방식을 이해하는 데 도움이되는 도구 나 사고 과정이 있습니까?

전체 개념에 얽매이지 않고 누군가를 "아하, 알았다"수준으로 끌어 올리기 위해 할 수있는 좋은 방법은 무엇입니까? 기본적으로 유사한 시나리오를 드릴하십시오.


20
이 질문의 주제는 포인터를 이해하기 어렵다는 것입니다. 이 질문은 포인터가 다른 것보다 이해하기 어렵다는 증거를 제공하지 않습니다.
bmargulies

14
어쩌면 (CCC 언어로 코딩했기 때문에) 뭔가 빠졌을 수도 있지만 메모리의 포인터가 키-> 값 구조로 생각되는지 항상 생각했습니다. 프로그램에서 많은 양의 데이터를 전달하는 것은 비용이 많이 들기 때문에 키는 큰 구조를 훨씬 작게 표현하기 때문에 구조 (값)를 만들고 포인터 / 참조 (키)를 전달합니다. 어려운 부분은 두 개의 포인터 / 참조 (키 또는 값을 비교하고 있습니까)를 비교 해야하는 경우 구조 (값)에 포함 된 데이터를 분석하기 위해 더 많은 작업이 필요합니다.
Evan Plaice 2019

2
@ Wolfpack'08 "주소의 메모리는 항상 정수인 것 같습니다." - 메모리에 비트가 있기 때문에 유형 이 없는 것으로 보입니다 . "실제로, 포인터의 타입은 포인터가 가리키는 var의 타입입니다"-아니오, 포인터의 타입은 포인터가리키는 var의 타입에 대한 포인터입니다.-자연스럽고 분명해야합니다.
짐 Balter

2
변수 (및 함수)가 단지 메모리 블록이고 포인터는 메모리 주소를 저장하는 변수라는 사실을 이해하기 어려운 것이 항상 궁금했습니다. 이것은 아마도 실제적인 사고 모델이 추상적 개념의 모든 팬들에게 감동을 줄 수는 없지만, 포인터의 작동 방식을 이해하는 데 완벽하게 도움이됩니다.
Christian Rau

8
간단히 말해서, 학생들은 컴퓨터의 일반적인 메모리, 특히 C "메모리 모델" 이 올바르게 작동하거나 전혀 이해하지 못하기 때문에 이해하지 못할 수도 있습니다. 이 책 은 기초부터 프로그래밍은 이러한 주제에 대한 아주 좋은 교훈을 제공합니다.
Abbafei

답변:


745

포인터는 많은 사람들이 특히 포인터 값을 복사하고 여전히 동일한 메모리 블록을 참조 할 때 혼동 될 수 있다는 개념입니다.

가장 좋은 비유는 포인터를 집 주소가있는 종이 조각으로 간주하고 실제 블록으로 참조하는 메모리 블록을 고려하는 것입니다. 따라서 모든 종류의 작업을 쉽게 설명 할 수 있습니다.

아래에 Delphi 코드를 추가하고 적절한 경우 주석을 추가했습니다. 다른 주요 프로그래밍 언어 인 C #은 동일한 방식으로 메모리 누수와 같은 것을 나타내지 않기 때문에 Delphi를 선택했습니다.

높은 수준의 포인터 개념 만 배우려면 아래 설명에서 "메모리 레이아웃"이라고 표시된 부분을 무시해야합니다. 그것들은 작업 후 메모리가 어떻게 보일 수 있는지에 대한 예를 제공하기 위해 의도되었지만 더 낮은 수준입니다. 그러나 버퍼 오버런의 실제 작동 방식을 정확하게 설명하기 위해이 다이어그램을 추가하는 것이 중요했습니다.

면책 조항 : 모든 의도와 목적을 위해이 설명과 예제 메모리 레이아웃은 크게 단순화되었습니다. 낮은 수준의 메모리를 처리해야하는 경우 알아야 할 오버 헤드와 세부 정보가 더 많습니다. 그러나 메모리와 포인터를 설명하기 위해 충분히 정확합니다.


아래에 사용 된 THouse 클래스가 다음과 같다고 가정 해 봅시다.

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

하우스 오브젝트를 초기화하면 생성자에 지정된 이름이 개인 필드 FName에 복사됩니다. 고정 크기 배열로 정의 된 이유가 있습니다.

메모리에서는 하우스 할당과 관련된 약간의 오버 헤드가있을 것입니다.이를 아래와 같이 설명하겠습니다.

--- [ttttNNNNNNNNNN] ---
     ^ ^
     | |
     | +-FName 배열
     |
     +-오버 헤드

"tttt"영역은 오버 헤드이며, 일반적으로 8 또는 12 바이트와 같은 다양한 유형의 런타임 및 언어에 대해 더 많은 부분이 있습니다. 이 영역에 저장된 값이 메모리 할당 자나 코어 시스템 루틴 이외의 다른 것으로 변경되지 않아야합니다. 그렇지 않으면 프로그램이 충돌 할 위험이 있습니다.


메모리 할당

기업가에게 집을 짓고 집 주소를 알려주십시오. 실제와 달리 메모리 할당은 어디에 할당해야하는지 알 수 없지만 충분한 공간이있는 적절한 지점을 찾고 할당 된 메모리에 주소를 다시보고합니다.

다시 말해, 기업가는 그 자리를 선택할 것입니다.

THouse.Create('My house');

메모리 레이아웃 :

--- [ttttNNNNNNNNNN] ---
    1234 우리집

주소로 변수를 유지

새 집 주소를 종이에 적는다. 이 문서는 집을 참조 할 것입니다. 이 종이가 없으면 잃어 버렸으며 이미 집에 있지 않는 한 집을 찾을 수 없습니다.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

메모리 레이아웃 :

    h
    V
--- [ttttNNNNNNNNNN] ---
    1234 우리집

포인터 값 복사

새 종이에 주소를 쓰십시오. 이제 두 개의 별도 용지가 아닌 동일한 집으로가는 두 개의 종이가 있습니다. 한 종이의 주소를 따르고 그 집의 가구를 재 배열하려고 하면 다른 집도 같은 방식으로 수정 된 것처럼 보이지만 실제로는 한 집이라는 것을 명백하게 감지 할 수 없다면 말입니다.

참고 이것은 일반적으로 사람들에게 설명하는 데 가장 문제가 많은 개념이며, 두 개의 포인터가 두 개의 객체 또는 메모리 블록을 의미하지는 않습니다.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    V
--- [ttttNNNNNNNNNN] ---
    1234 우리집
    ^
    h2

메모리 확보

집을 철거하십시오. 그런 다음 나중에 원할 경우 신문을 새 주소로 재사용하거나 더 이상 존재하지 않는 집 주소를 잊어 버리도록 지울 수 있습니다.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

여기에서 먼저 집을 짓고 주소를 붙잡습니다. 그런 다음 집에 뭔가를하고 (독자에게 연습으로 남겨둔 ... 코드를 사용하십시오) 그런 다음 무료로 사용하십시오. 마지막으로 변수에서 주소를 지 웁니다.

메모리 레이아웃 :

    h <-+
    v +-무료 전
--- [ttttNNNNNNNNNN] --- |
    1234 내 집 <-+

    h (지금은 아무데도) <-+
                                +-무료 후
---------------------- | (메모리는 여전히
    xx34 내 집 <-+에 데이터가 들어 있습니다)

매달려있는 포인터

당신은 기업가에게 집을 파괴하라고 지시하지만, 종이에서 주소를 지우는 것을 잊어 버립니다. 나중에 종이 조각을 보면 집이 더 이상 존재하지 않으며 방문하지 않고 결과가 실패한다는 것을 잊었습니다 (아래 잘못된 참조에 대한 부분도 참조하십시오).

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

h부름을받은 후에 사용하면 효과 가 .Free 있을 수 있지만 그것은 단지 행운입니다. 중요한 작업 중에는 고객 위치에서 실패 할 가능성이 높습니다.

    h <-+
    v +-무료 전
--- [ttttNNNNNNNNNN] --- |
    1234 내 집 <-+

    h <-+
    무료 후 v +-
---------------------- |
    xx34 나의 집 <-+

보시다시피 h는 여전히 메모리에있는 데이터의 나머지를 가리 킵니다. 그러나 데이터가 완전하지 않기 때문에 이전과 같이 사용하지 못할 수 있습니다.


메모리 누수

종이 한 장을 잃어 집을 찾을 수 없습니다. 집은 여전히 ​​어딘가에 서 있으며 나중에 새 집을 짓고 싶을 때 그 자리를 재사용 할 수 없습니다.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

여기서 우리 h는 새 집의 주소로 변수 의 내용을 덮어 썼지 만 오래된 집은 여전히 ​​어딘가에 서 있습니다. 이 코드가 끝나면 그 집에 갈 수있는 방법이 없으며, 그대로 남아있을 것입니다. 다시 말해, 할당 된 메모리는 응용 프로그램이 닫힐 때까지 할당 된 상태를 유지하며,이 시점에서 운영 체제가이를 해제합니다.

첫 번째 할당 후 메모리 레이아웃 :

    h
    V
--- [ttttNNNNNNNNNN] ---
    1234 우리집

두 번째 할당 후 메모리 레이아웃 :

                       h
                       V
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234 나의 집 5678 나의 집

이 방법을 얻는 더 일반적인 방법은 위와 같이 덮어 쓰지 않고 해제하는 것을 잊어 버리는 것입니다. 델파이 용어로 이것은 다음과 같은 방법으로 발생합니다.

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

이 방법을 실행 한 후에는 변수에 집 주소가 존재하지만 집은 여전히 ​​존재하지 않습니다.

메모리 레이아웃 :

    h <-+
    포인터를 잃기 전에 v +-
--- [ttttNNNNNNNNNN] --- |
    1234 내 집 <-+

    h (지금은 아무데도) <-+
                                +-포인터를 잃은 후
--- [ttttNNNNNNNNNN] --- |
    1234 내 집 <-+

보다시피, 이전 데이터는 메모리에 그대로 남아 있으며 메모리 할당 자에 의해 재사용되지 않습니다. 할당자는 사용 된 메모리 영역을 추적하며 해제하지 않으면 재사용하지 않습니다.


메모리를 비우지 만 (현재 유효하지 않은) 참조는 유지

집을 철거하고, 종이 한 장을 지울 수 있지만, 오래된 주소를 가진 또 다른 종이가 있습니다. 주소로 갈 때 집을 찾을 수는 없지만 폐허와 비슷한 것을 찾을 수 있습니다 하나의.

아마도 당신은 심지어 집을 찾을 것입니다, 그러나 그것은 당신이 원래 주소를 받았던 집이 아니기 때문에, 당신이 속한 것처럼 그것을 사용하려는 시도는 끔찍하게 실패 할 수 있습니다.

때로는 이웃 주소에 3 개의 주소 (메인 스트리트 1 ~ 3)를 차지하는 다소 큰 집이 설정되어 있고 주소가 집 가운데로 이동하는 경우도 있습니다. 큰 3 주소 집의 일부를 하나의 작은 집으로 취급하려는 시도도 끔찍하게 실패 할 수 있습니다.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

여기에 집에서 참조를 통해, 철거되었다 h1,하는 동안 h1뿐만 아니라 지워졌습니다, h2여전히 이전, 아웃 오브 날짜, 주소가 있습니다. 더 이상 서 있지 않은 집에 대한 접근은 작동하지 않거나 작동하지 않을 수 있습니다.

위의 매달려있는 포인터의 변형입니다. 메모리 레이아웃을 참조하십시오.


버퍼 오버런

당신은 이웃 집이나 마당에 쏟아져 들어갈 수있는 것보다 더 많은 물건을 집으로 옮깁니다. 그 이웃 집의 소유자가 나중에 집으로 돌아 오면, 자신이 고려할 모든 종류의 물건을 찾을 수 있습니다.

이것이 내가 고정 크기 배열을 선택한 이유입니다. 무대를 설정하기 위해, 우리가 할당 한 두 번째 집은 어떤 이유로 메모리의 첫 번째 집 앞에 위치한다고 가정하십시오. 즉, 두 번째 집은 첫 번째 집보다 주소가 낮습니다. 또한 서로 바로 옆에 할당됩니다.

따라서이 코드는 다음과 같습니다.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

첫 번째 할당 후 메모리 레이아웃 :

                        h1
                        V
----------------------- [ttttNNNNNNNNNN]
                        내 집

두 번째 할당 후 메모리 레이아웃 :

    h2 h1
    vv
--- [ttttNNNNNNNNNN] ---- [ttttNNNNNNNNNN]
    1234 어딘가에있는 다른 집
                        ^ --- +-^
                            |
                            +-덮어 쓰기

가장 자주 충돌을 일으키는 부분은 저장된 데이터의 중요한 부분을 덮어 쓰는 경우 실제로 임의로 변경해서는 안됩니다. 예를 들어, 프로그램 충돌과 관련하여 h1 하우스 이름의 일부가 변경되었지만 문제가되지는 않지만 깨진 개체를 사용하려고 할 때 개체의 오버 헤드를 덮어 쓰면 충돌이 발생할 가능성이 높습니다. 객체의 다른 객체에 저장된 링크를 덮어 씁니다.


연결된 목록

종이 한 장의 주소를 따라 가면 집에 도착하고 그 집에는 체인의 다음 집 등을 위해 새 주소를 가진 또 다른 종이가 있습니다.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

여기서 우리는 우리 집과 오두막으로 연결되는 링크를 만듭니다. 우리는 집이 NextHouse참조 가 없을 때까지 체인을 따라갈 수 있습니다 . 모든 집을 방문하기 위해 다음 코드를 사용할 수 있습니다.

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

메모리 레이아웃 (다음 다이어그램에서 객체에 링크로 NextHouse 추가, 아래 다이어그램에서 4 개의 LLLL로 표시됨) :

    h1 h2
    vv
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234 홈 + 5678 캐빈 +
                   | ^ |
                   + -------- + * (링크 없음)

기본적으로 메모리 주소는 무엇입니까?

메모리 주소는 기본적으로 숫자입니다. 메모리를 큰 바이트 배열로 생각하면 첫 번째 바이트의 주소는 0이고 다음 바이트의 주소는 1입니다. 이것은 간단하지만 충분합니다.

따라서이 메모리 레이아웃 :

    h1 h2
    vv
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234 나의 집 5678 나의 집

다음 두 주소를 가질 수 있습니다 (가장 왼쪽-주소 0).

  • h1 = 4
  • h2 = 23

위의 링크 된 목록이 실제로 다음과 같이 보일 수 있습니다.

    h1 (= 4) h2 (= 28)
    vv
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234 홈 0028 5678 카빈 0000
                   | ^ |
                   + -------- + * (링크 없음)

일반적으로 주소가 0 인 주소를 주소 0으로 저장하는 것이 일반적입니다.


기본적으로 포인터 란 무엇입니까?

포인터는 메모리 주소를 보유한 변수 일뿐입니다. 일반적으로 프로그래밍 언어에 번호를 제공하도록 요청할 수 있지만 대부분의 프로그래밍 언어 및 런타임은 숫자 자체가 실제로 의미를 갖지 않기 때문에 아래에 숫자가 있다는 사실을 숨기려고합니다. 포인터를 블랙 박스로 생각하는 것이 가장 좋습니다. 실제로 작동하는 한 실제로 구현되는 방법을 알거나 신경 쓰지 않습니다.


59
버퍼 오버런이 유쾌합니다. "이웃은 집에 와서 그의 두개골이 당신의 쓰레기에 미끄러 져서 당신을 망각으로 고소합니다."
gtd

12
이것은 개념에 대한 좋은 설명입니다. 개념은 포인터에 대해 혼란스러워하는 것이 아니므 로이 전체 에세이는 약간 낭비되었습니다.
Breton

10
그러나 단지 무엇을 물어 가지고 당신이 포인터에 대해 혼란을 찾을?
Lasse V. Karlsen

11
답변을 작성한 후이 게시물을 여러 번 다시 방문했습니다. 코드에 대한 설명이 훌륭하며 더 많은 생각을 추가 / 정의하기 위해 다시 방문해 주셔서 감사합니다. 브라보 라세!
David McGraw 2016 년

3
한 페이지의 텍스트 (길이에 상관없이)가 메모리 관리, 참조, 포인터 등의 모든 뉘앙스를 요약 할 수있는 방법은 없습니다. 정보의 시작 페이지. 더 배울 것이 있습니까? 물론 그렇지 않습니다.
Lasse V. Karlsen

153

첫 번째 Comp Sci 수업에서는 다음과 같은 연습을했습니다. 물론, 여기에는 약 200 명의 학생이있는 강의실이 있습니다.

교수는 칠판에 다음과 같이 씁니다. int john;

존이 일어

교수는 다음과 같이 썼다. int *sally = &john;

샐리는 일어 서서 요한을 가리킨다

교수: int *bill = sally;

빌이 일어서 요

교수: int sam;

샘은 일어

교수: bill = &sam;

Bill은 이제 Sam을 가리 킵니다.

나는 당신이 아이디어를 얻는다고 생각합니다. 포인터 할당의 기본 사항을 살펴볼 때까지 한 시간 정도 걸렸다 고 생각합니다.


5
내가 잘못했다고 생각하지 않습니다. 내 의도는 지적 변수의 값을 John에서 Sam으로 변경하는 것이 었습니다. 두 포인터의 값을 변경하는 것처럼 보이기 때문에 사람들과 표현하기가 조금 더 어렵습니다.
Tryke

24
그러나 혼동되는 이유는 존이 자리에서 일어나서 샘이 앉는 것과 같지 않기 때문입니다. 그것은 샘이 와서 존에게 손을 댔고, 휴고 직조 행렬과 같이 샘 프로그래밍을 존의 몸에 복제 한 것과 비슷합니다.
Breton

59
샘이 요한의 자리를 차지하는 것과 마찬가지로 요한은 중요한 무언가에 부딪 히고 segfault를 일으킬 때까지 방을 떠 다닙니다.
just_wes

2
개인적 으로이 예제는 불필요하게 복잡합니다. 교수님이 저에게 빛을 가리켜달라고 하셨다.
Celeritas

이 유형의 예제의 문제점은 X와 X에 대한 포인터가 동일하지 않다는 것입니다. 그리고 이것은 사람들과 묘사되지 않습니다.
Isaac Nequittepas

124

포인터를 설명하는 데 도움이되는 유추는 하이퍼 링크입니다. 대부분의 사람들은 웹 페이지의 링크가 인터넷의 다른 페이지를 가리킴을 이해하고 해당 하이퍼 링크를 복사하여 붙여 넣을 수 있으면 둘 다 동일한 원본 웹 페이지를 가리 킵니다. 원래 페이지로 이동하여 편집 한 경우 해당 링크 (포인터) 중 하나를 따라 가면 새 업데이트 페이지가 표시됩니다.


15
나는 정말로 이것을 좋아한다. 하이퍼 링크를 두 번 작성한다고해서 두 개의 웹 사이트가 나타나지 int *a = b는 않습니다 (두 개의 복사본을 만들지 않는 것처럼 *b).
detly

4
이것은 실제로 매우 직관적이며 모든 사람이 관련시킬 수있는 것입니다. 이 비유가 다른 시나리오가 많이 있지만. 빠른 소개에 좋습니다. +1
Brian Wigginton

두 번 열리는 페이지에 대한 링크는 일반적으로 해당 웹 페이지의 거의 독립적 인 두 인스턴스를 만듭니다. 하이퍼 링크는 생성자와는 비슷하지만 포인터는 아닙니다.
Utkan Gezer

@ThoAppelsin 예를 들어 정적 html 웹 페이지에 액세스하는 경우 서버의 단일 파일에 액세스하는 것은 사실이 아닙니다.
dramzy

5
당신은 그것을 너무 생각하고 있습니다. 하이퍼 링크는 서버의 파일을 가리키며, 그 정도는 유사합니다.
dramzy

48

포인터가 너무 많은 사람들을 혼동하는 것처럼 보이는 이유는 대부분 컴퓨터 아키텍처에 대한 배경 지식이 거의 없거나 전혀 없기 때문입니다. 많은 사람들이 컴퓨터 (기계)가 실제로 어떻게 구현되는지에 대한 아이디어를 가지고 있지 않기 때문에 C / C ++에서 작업하는 것은 외계인처럼 보입니다.

포인터 조작 (로드, 저장, 직접 / 간접 주소 지정)에 중점을 둔 명령 세트를 사용하여 간단한 바이트 코드 기반 가상 머신 (선택한 언어로 파이썬이 가장 효과적 임)을 구현하도록 요청하는 것이 좋습니다. 그런 다음 해당 명령 세트에 대한 간단한 프로그램을 작성하도록 요청하십시오.

단순한 추가보다 약간 더 필요한 것은 포인터를 포함 할 것이고 그것을 얻을 것이라고 확신합니다.


2
흥미 롭군 그래도 어떻게 시작 해야할지 모르겠다. 공유 할 자원이 있습니까?
Karolis

1
동의한다. 예를 들어, C 이전에 어셈블리에서 프로그래밍하는 방법을 배우고 레지스터가 작동하는 방식을 알고 포인터를 쉽게 배울 수있었습니다. 실제로 많은 학습이 없었으며 모두 자연스럽게 이루어졌습니다.
Milan Babuškov 2016 년

기본 CPU를 가지고 잔디 깍는 기계 또는 식기 세척기를 실행하는 것을 말하고 구현하십시오. 또는 매우 기본적인 ARM 또는 MIPS 하위 집합입니다. 둘 다 매우 간단한 ISA를 가지고 있습니다.
Daniel Goldberg

1
이 교육 접근법은 Donald Knuth 자신이 옹호 / 실천했음을 지적 할 가치가 있습니다. Knuth의 컴퓨터 프로그래밍 기술은 간단한 가상 아키텍처를 설명하고 학생들에게 해당 아키텍처에 대한 가상 어셈블리 언어로 문제를 연습 할 수있는 솔루션을 구현하도록 요청합니다. 그것이 실제로 실현 가능 해지면 Knuth의 책을 읽는 일부 학생들은 실제로 자신의 아키텍처를 VM으로 구현하거나 기존 구현을 사용하여 실제로 솔루션을 실행합니다. IMO는 시간이 있다면 배우는 좋은 방법입니다.
WeirdlyCheezy 2011

4
@Luke 나는 단순히 포인터를 이해할 수없는 사람들을 이해하기가 쉽지 않다고 생각합니다 (또는 일반적으로 더 정확하고 간접적으로). 기본적으로 C의 포인터를 이해하지 못하는 사람들은 어셈블리 학습을 시작하고 컴퓨터의 기본 아키텍처를 이해하고 포인터를 이해하여 C로 돌아갈 수 있다고 가정합니다. 이것은 많은 사람들에게 맞을지 모르지만 일부 연구에 따르면 일부 사람들은 원칙적으로도 간접적으로 간접적으로 파악할 수없는 것 같습니다 (아직도 믿기가 매우 어렵다는 것을 알지만 아마도 나는 "학생들에게 운이 좋았습니다. ").
Luaan

27

C / C ++ 언어를 사용하는 새롭고 나이 많은 대학 수준의 학생들에게 포인터가 왜 혼란의 주요 요인입니까?

가치에 대한 자리 표시 자의 개념-변수-은 학교에서 우리가 가르치는 무언가-대수에 매핑됩니다. 컴퓨터 내에서 실제로 메모리가 배치되는 방식을 이해하지 않고 그릴 수있는 기존의 병렬 요소는 없으며 C / C ++ / 바이트 통신 수준에서 낮은 수준의 문제를 처리 할 때까지는 이런 종류의 것을 생각하지 않습니다. .

변수, 함수 및 수준을 넘어서 포인터가 작동하는 방식을 이해하는 데 도움이되는 도구 나 사고 과정이 있습니까?

주소 상자. BASIC을 마이크로 컴퓨터로 프로그래밍하는 법을 배웠을 때, 게임에 담긴이 예쁜 책들이 있었으며 때로는 특정 주소에 가치를 쏟아야했습니다. 그들은 0, 1, 2로 점진적으로 레이블이 붙은 많은 상자 그림을 가지고 있었으며 작은 상자 (1 바이트)만이 상자에 들어갈 수 있으며 많은 컴퓨터가 있다고 설명했습니다. 일부 컴퓨터 65535만큼이나 있었다! 그들은 서로 옆에 있었고 모두 주소를 가지고있었습니다.

전체 개념에 얽매이지 않고 누군가를 "아하, 알았다"수준으로 끌어 올리기 위해 할 수있는 좋은 방법은 무엇입니까? 기본적으로 유사한 시나리오를 드릴하십시오.

훈련? 구조체 만들기 :

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

C를 제외하고 위와 동일한 예 :

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

산출:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

아마도 그것은 예제를 통해 몇 가지 기본 사항을 설명합니까?


"메모리가 물리적으로 배치되는 방법을 이해하지 않고"+1 어셈블리 언어 배경에서 C로 왔으며 포인터의 개념은 매우 자연스럽고 쉽습니다. 그리고 더 높은 수준의 언어 배경을 가진 사람들이 그것을 이해하는 데 어려움을 겪었습니다. 설상가상으로 구문이 혼란 스러우므로 (함수 포인터!) 개념과 구문을 동시에 배우는 것이 문제를위한 레시피입니다.
Brendan

4
나는 이것이 오래된 게시물이라는 것을 알고 있지만 제공된 코드의 출력이 게시물에 추가되면 좋을 것입니다.
Josh

예, 대수와 비슷합니다 (대수는 "변수"를 변경할 수 없다는 점에서 추가로 이해하기 쉬운 점이 있습니다). 그러나 내가 아는 사람들의 약 절반은 실제로 대수학을 이해하지 못합니다. 단지 그들을 위해 계산하지 않습니다. 그들은 그 결과에 도달하기위한 모든 "방정식"과 처방전을 알고 있지만, 그것들을 다소 무작위로 서투르게 적용합니다. 그리고 그들은 그들 자신의 목적을 위해 그것들을 확장 할 수 없습니다 -그것은 그것들을 위해 변경 불가능하고 구성 할 수없는 블랙 박스 일뿐입니다. 대수학을 이해하고 효과적으로 사용할 수 있다면, 프로그래머들조차도 이미 앞서고 있습니다.
Luaan

24

처음에 포인터를 이해하는 데 어려움을 겪었던 이유는 많은 설명에 참조로 전달하는 것에 대한 많은 쓰레기가 포함되어 있기 때문입니다. 이 모든 것이 문제를 혼동하기 만합니다. 포인터 매개 변수를 사용하면 여전히 값으로 전달됩니다. 그러나 값은 int가 아닌 주소입니다.

다른 사람이 이미이 자습서에 연결했지만 포인터를 이해하기 시작한 순간을 강조 할 수 있습니다.

C의 포인터와 배열에 대한 튜토리얼 : 3 장-포인터와 문자열

int puts(const char *s);

현재 const.전달 된 매개 변수 puts()는 포인터입니다. 포인터 값은 포인터의 값입니다 (C의 모든 매개 변수는 값으로 전달되므로). 포인터의 값은 포인터가 가리키는 주소입니다. , 주소. 따라서 puts(strA);우리가 본 것처럼 쓰면 strA [0]의 주소를 전달하게됩니다.

이 단어를 읽는 순간 구름이 갈라지고 햇빛 광선이 포인터를 이해하게했습니다.

VB .NET 또는 C # 개발자이고 (현재 그대로) 안전하지 않은 코드를 사용하지 않더라도 포인터의 작동 방식을 이해하는 것이 가치가 있거나 객체 참조의 작동 방식을 이해하지 못합니다. 그런 다음 객체 참조를 메서드에 전달하면 객체가 복사된다는 일반적인 실수가 발생합니다.


포인터를 갖는 요점이 무엇인지 궁금해합니다. 내가 만나는 대부분의 코드 블록에서 포인터는 선언에서만 볼 수 있습니다.
Wolfpack'08

@ Wolfpack'08 ... 뭐 ?? 어떤 코드를보고 있습니까?
Kyle Strand

@KyleStrand 한번 봅시다.
Wolfpack'08

19

Ted Jensen의 "C의 포인터 및 배열에 대한 자습서"는 포인터에 대한 학습을위한 훌륭한 리소스입니다. 그것은 포인터가 무엇인지 (그리고 무엇을 위해 무엇인지) 설명하고 함수 포인터로 마무리하는 10 개의 수업으로 나뉩니다. http://home.netcom.com/~tjensen/ptr/cpoint.htm

거기에서 Beej 's Guide to Network Programming은 유닉스 소켓 API를 가르치며, 여기에서 정말 재미있는 일을 시작할 수 있습니다. http://beej.us/guide/bgnet/


1
두 번째 Ted Jensen의 튜토리얼입니다. 그것은 상세 수준에 대한 포인터를 세분화합니다. 매우 도움이됩니다! :)
Dave Gallagher

12

포인터의 복잡성은 우리가 쉽게 가르 칠 수있는 것 이상입니다. 학생들이 서로를 가리 키도록하고 집 주소와 함께 종이 조각을 사용하는 것은 훌륭한 학습 도구입니다. 그들은 기본 개념을 소개하는 훌륭한 일을합니다. 실제로 기본 개념을 배우는 것은 포인터를 성공적으로 사용 중요 합니다. 그러나 프로덕션 코드에서는 이러한 간단한 데모가 캡슐화 할 수있는 것보다 훨씬 더 복잡한 시나리오를 사용하는 것이 일반적입니다.

다른 구조물을 가리키는 구조물이 다른 구조물을 가리키는 시스템에 관여했습니다. 이러한 구조 중 일부에는 추가 구조에 대한 포인터가 아니라 내장 구조가 포함되어 있습니다. 이것은 포인터가 실제로 혼란스러워하는 곳입니다. 여러 수준의 간접 참조가 있고 다음과 같은 코드로 끝나기 시작합니다.

widget->wazzle.fizzle = fazzle.foozle->wazzle;

정말 빨리 혼란 스러울 수 있습니다 (더 많은 줄과 잠재적으로 더 많은 레벨을 상상하십시오). 포인터 배열과 노드 대 노드 포인터 (트리, 링크 된 목록)를 던져 넣으면 여전히 나빠집니다. 나는 그러한 시스템에서 일하기 시작하면 정말 좋은 개발자들이 길을 잃는 것을 보았습니다.

포인터의 복잡한 구조는 코딩이 제대로 표시되지 않을 수도 있습니다. 컴포지션은 훌륭한 객체 지향 프로그래밍의 중요한 부분이며, 원시 포인터가있는 언어에서는 필연적으로 다층 간접 처리로 이어질 것입니다. 또한 시스템은 종종 스타일이나 기법이 서로 일치하지 않는 구조의 타사 라이브러리를 사용해야합니다. 그러한 상황에서는 복잡성이 자연스럽게 발생할 것입니다 (물론 우리는 가능한 한 많이 싸워야합니다).

학생들이 포인터를 배우도록 돕기 위해 대학이 할 수있는 최선의 방법은 포인터 사용이 필요한 프로젝트와 함께 훌륭한 데모를 사용하는 것입니다. 하나의 어려운 프로젝트는 수천 개의 데모보다 포인터 이해에 더 많은 역할을합니다. 데모를 통해 이해를 깊게 할 수 있지만 포인터를 깊이 이해하려면 실제로 사용해야합니다.


10

나는이 목록에 비유를 추가하여 컴퓨터 과학 교사로서 포인터를 설명 할 때 매우 유용하다고 생각했다. 먼저 :


무대를 설정 :

3 개의 공간이있는 주차장을 고려하십시오.이 공간에는 번호가 매겨집니다.

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

어떤 방식으로, 이것은 메모리 위치와 같으며 순차적이고 연속적입니다. 일종의 배열과 같습니다. 지금은 자동차가 없으므로 빈 배열과 같습니다 ( parking_lot[3] = {0}).


데이터 추가

주차장은 오랫동안 빈 ​​상태로 유지되지 않습니다. 만약 그렇게한다면 아무 의미가없고 아무도 만들지 않을 것입니다. 하루가 이동함에 따라 3 대의 자동차, 파란 차, 빨간 차 및 녹색 차가 가득 차 있다고 가정 해 봅시다.

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

이 차는 모든 생각하는 한 가지 방법은 우리의 자동차 데이터의 어떤 종류 (AN 말 것을 그래서 동일한 유형 (자동차)입니다 int)하지만 그들은 서로 다른 값이 ( blue, red, green, 색이 될 수 있었다 enum)


포인터를 입력

이 주차장에 당신을 데려 가서 파란 차를 찾도록 요청하면, 손가락 하나를 뻗어 그 자리에서 파란 차를 가리 키도록 사용하십시오. 이것은 포인터를 가져다가 메모리 주소에 할당하는 것과 같습니다. (int *finger = parking_lot )

당신의 손가락 (포인터)은 내 질문에 대한 답이 아닙니다. 상대 손가락은 나에게 아무것도 알 수 없지만, 난 당신이있는 거 손가락이 위치를 보면 가리키는 (포인터를 역 참조), 내가 찾고 있던 차 (데이터를) 찾을 수 있습니다.


포인터 재 지정

이제 빨간 차를 찾아달라고 요청할 수 있으며 손가락을 새 차로 리디렉션 할 수 있습니다. 이제 포인터 (이전과 동일한 것)가 동일한 유형 (차)의 새로운 데이터 (빨간 차를 찾을 수있는 주차 장소)를 보여줍니다.

포인터는 물리적으로는 여전히 변경하지 않은 당신의 손가락, 그것은 나를 변화 보여주는 된 데이터 만. ( 「주차장」주소)


이중 포인터 (또는 포인터에 대한 포인터)

이것은 둘 이상의 포인터에서도 작동합니다. 빨간 차를 가리키는 포인터가 어디에 있는지 물어볼 수 있으며 다른 손을 사용하고 손가락으로 첫 번째 손가락을 가리킬 수 있습니다. (이것은int **finger_two = &finger )

이제 파란 차가 어디에 있는지 알고 싶다면 첫 번째 손가락의 방향을 따라 두 번째 손가락, 차 (데이터)를 따라갈 수 있습니다.


매달려있는 포인터

이제 동상과 매우 흡사하다고 말하고 빨간 차를 무기한으로 향하는 손을 잡으려고합니다. 그 빨간 차가 도망 가면 어떨까요?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

포인터는 여전히 빨간 차 어디를 가리키는 없었다 하지만 더 이상. 새 차가 들어 오면 ... 오렌지 차가 있다고합시다. "빨간 차는 어디에 있습니까?"라는 질문을 다시해도 여전히 거기에있는 것입니다. 그것은 빨간 차가 아니라 주황색입니다.


포인터 산술

그래, 여전히 두 번째 주차 지점을 가리키고 있습니다 (오렌지 자동차가 점령)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

글쎄요, 지금 새로운 질문이 있습니다 ... 다음 주차 장소 에서 차의 색을 알고 싶습니다 . 2 지점을 가리키고 있음을 알 수 있으므로 1을 추가하고 다음 지점을 가리키고 있습니다. ( finger+1), 이제 데이터가 무엇인지 알고 싶었으므로 손가락이 아닌 해당 지점을 확인해야 포인터 ( *(finger+1))를 연기하여 녹색 자동차가 있는지 확인할 수 있습니다 (해당 위치의 데이터) )


"이중 포인터"라는 단어를 사용하지 마십시오. 포인터는 무엇이든 가리킬 수 있으므로 다른 포인터를 가리키는 포인터를 가질 수 있습니다. 그것들은 이중 포인터가 아닙니다.
gnasher729

나는 이것이“손가락을 찌르는 자”자신이 당신의 비유를 계속하기 위해 각각“주차장을 점유”한다는 점을 놓치고 있다고 생각합니다. 사람들이 당신의 비유에 대한 추상화의 높은 수준에서 포인터를 이해하는 데 어려움이 있는지 확실하지 않습니다. 포인터가 메모리 위치를 차지하는 변경 가능한 것들이며, 이것이 유용한 방법으로 사람들을 피하는 것처럼 이해합니다.
Emmet

1
@Emmet-WRT 포인터에 더 많은 것이 있다는 데 동의하지 않지만 "without getting them bogged down in the overall concept"높은 수준의 이해로 질문을 읽습니다 . 그리고 당신의 요점 : "I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"-당신은 이 수준까지 포인터를 이해 하지 못하는 사람들 얼마나 놀랐는지 – 놀라서
Mike

자동차 핑거링 비유를 사람 (하나 이상의 손가락으로)과 각각의 방향을 가리킬 수있는 유전 적 이상으로 확장하는 장점이 있습니까? 또는 다른 자동차를 가리키는 자동차 중 하나에 앉아있었습니다. "초기화되지 않은 포인터"로 로트 옆에있는 황무지를 가리 키거나, 전체 손이 "고정 된 크기 [5] 포인터의 배열"로서 한 줄의 행을 가리 키거나 손바닥 "널 포인터"에 웅크 리고 그것은 자동차가 없다고 알려진 곳을 가리 킵니다.) ... 8-)
SlySven

당신의 설명은 훌륭하고 초보자에게는 좋은 설명이었습니다.
Yatendra Rathore

9

나는 개념으로서의 포인터가 특히 까다 롭다고 생각하지 않습니다. 대부분의 학생들의 정신 모델은 이와 같은 것에 매핑되며 일부 빠른 상자 스케치가 도움이 될 수 있습니다.

적어도 내가 과거에 경험하고 다른 사람들이 다루는 것을 본 어려움은 C / C ++에서 포인터 관리가 불필요하게 복잡해질 수 있다는 것입니다.


9

좋은 다이어그램 세트가있는 튜토리얼의 예는 포인터를 이해하는 데 크게 도움이됩니다 .

Joel Spolsky는 게릴라 인터뷰 가이드 기사 에서 포인터 이해에 대해 다음과 같이 좋은 점을 지적합니다 .

어떤 이유로 든 대부분의 사람들은 포인터를 이해하는 두뇌의 일부없이 태어난 것처럼 보입니다. 이것은 기술적 인 것이 아니라 적성적인 것입니다. 어떤 사람들은 할 수 없다는 복잡한 형태의 이중 간접 사고가 필요합니다.


8

포인터의 문제는 개념이 아닙니다. 실행과 관련된 언어입니다. 교사들이 전문 용어가 아니라 어려운 포인터의 개념이라고 생각하거나 복잡한 C 및 C ++가 개념을 만든다고 가정 할 때 혼란이 추가로 발생합니다. 개념을 설명하는 데 많은 노력이 필요하지 않습니다 (이 질문에 대한 대답과 같이). 나는 이미 그 모든 것을 이해하기 때문에 나와 같은 누군가에게 낭비됩니다. 문제의 잘못된 부분을 설명하고 있습니다.

내가 어디에서 왔는지 알기 위해 포인터를 완벽하게 이해하는 사람이며 어셈블러 언어로 유능하게 사용할 수 있습니다. 어셈블러 언어에서는 포인터라고하지 않습니다. 이를 주소라고합니다. C에서 프로그래밍하고 포인터를 사용할 때 많은 실수를하고 혼란스러워합니다. 나는 아직도 이것을 정리하지 않았다. 예를 들어 보겠습니다.

API가 말하면 :

int doIt(char *buffer )
//*buffer is a pointer to the buffer

무엇을 원합니까?

그것은 원할 수 있습니다 :

버퍼의 주소를 나타내는 숫자

(그것을주기 위해 doIt(mybuffer), 또는 말 doIt(*myBuffer)합니까?)

주소의 주소를 버퍼의 주소로 나타내는 숫자

(즉 doIt(&mybuffer)하거나 doIt(mybuffer)또는 doIt(*mybuffer)?)

주소의 주소를 버퍼의 주소로 나타내는 숫자

(아마의 그 doIt(&mybuffer). 또는 그 것이다 doIt(&&mybuffer)? 또는doIt(&&&mybuffer) )

그리고 "x는 주소를 y로 유지합니다"와 "나를 의미하지 않습니다." 이 함수는 y "에 주소가 필요합니다. 답은 "mybuffer"가 무엇으로 시작해야하는지, 그리고 그것과 함께 무엇을 하려는지에 달려 있습니다. 이 언어는 실제로 발생하는 중첩 수준을 지원하지 않습니다. 새 포인터를 만드는 함수에 "포인터"를 넘겨야 할 때와 마찬가지로 버퍼의 새 위치를 가리 키도록 포인터를 수정합니다. 실제로 포인터 또는 포인터에 대한 포인터를 원하므로 포인터의 내용을 수정할 위치를 알고 있습니다. 대부분의 경우 "

"포인터"가 너무 오버로드되었습니다. 포인터가 값의 주소입니까? 또는 주소를 값으로 보유하는 변수입니까? 함수가 포인터를 원할 때 포인터 변수가 보유한 주소를 원합니까 아니면 포인터 변수에 주소를 원합니까? 혼란 스러워요.


나는 다음과 같이 설명했다 : 당신이 같은 포인터 선언을 본다면 double *(*(*fn)(int))(char), 평가 결과 *(*(*fn)(42))('x')double입니다. 중간 평가 유형을 이해하기 위해 평가 계층을 제거 할 수 있습니다.
Bernd Jendrissek

@BerndJendrissek 내가 확실하지 않습니다. (*(*fn)(42))('x') 그때 평가 한 결과는 무엇입니까 ?
Breton

x평가 *x하면 두 배가 되는 물건 을 얻습니다.
Bernd Jendrissek

@BerndJendrissek 이것이 포인터에 대해 설명해야합니까? 나는 그것을 얻지 못한다. 너의 요점이 뭐야? 레이어를 제거했으며 중간 유형에 대한 새로운 정보를 얻지 못했습니다. 특정 기능이 무엇을 받아 들일지에 대해 무엇을 설명합니까? 그것은 무엇과 관련이 있습니까?
Breton

아마이 설명에서 메시지 (그리고 나의 내가 찾을 수 있으면 좋겠다,없는 경우에는 그것이 내가 처음으로 톱) 적은 무엇의 관점에서 생각하는 것 fn 입니다 보다 당신이 할 수있는 측면에서 fn
번드 Jendrissek

8

나는 포인터를 이해하는데 주된 장벽은 나쁜 교사라고 생각합니다.

거의 모든 사람들은 포인터에 관해 거짓말을한다 : 그것들은 메모리 주소에 지나지 않으며 , 임의의 위치 를 가리킬 수있다 .

물론 이해하기 어렵고 위험하며 반 마술 적입니다.

어느 것도 사실이 아닙니다. C ++ 언어가 말하는 것에 충실하고 실제로 "작동하는"것으로 밝혀 지지만, 언어에 의해 보장되지는 않는 한, 포인터는 실제로 매우 단순한 개념 입니다. 실제 포인터 개념의 일부가 아닙니다.

몇 달 전에이 블로그 게시물이것에 대한 설명을 쓰려고 노력했습니다 .

C ++ 표준은 포인터 메모리 주소를 나타낸다고 말하지만 "포인터는 메모리 주소이며 메모리 주소 외에는 메모리와 상호 교환 가능하게 사용되거나 생각 될 수있다"고 말하는 것은 아닙니다. 주소 ". 구별이 중요합니다)


결국, 널 포인터는 C "값"이 0이지만 메모리에서 주소 0을 가리 키지 않습니다. 그것은 완전히 별개의 개념이며, 잘못 처리하면 예상치 못한 것을 해결 (및 역 참조) 할 수 있습니다. 어떤 경우에는 메모리에서 주소가 0 일 수도 있고 (특히 주소 공간이 일반적으로 평평한 경우), 다른 경우에는 최적화 컴파일러에 의해 정의되지 않은 동작으로 생략되거나 연결된 메모리의 다른 부분에 액세스 할 수 있습니다 주어진 포인터 유형에 대해 "제로". 성대가 계속됩니다.
Luaan

반드시 그런 것은 아닙니다. 포인터가 의미가 있고 다른 프로그램도 디버깅 할 수 있도록 컴퓨터를 머리에 모델링 할 수 있어야합니다. 모든 사람이 이것을 할 수있는 것은 아닙니다.
Thorbjørn Ravn Andersen 님이

5

포인터를 배우기가 까다로워지는 것은 포인터까지 "이 메모리 위치는 int, double, character 등을 나타내는 비트 세트"라는 생각에 익숙하다는 것입니다.

처음 포인터를 볼 때 실제로 해당 메모리 위치에있는 것을 얻지 못합니다. "무엇을 의미 합니까? 주소를 보유하고 있습니까?"

나는 "당신은 그것들을 얻거나 얻지 못한다"는 개념에 동의하지 않습니다.

큰 구조를 함수에 전달하지 않는 것처럼 실제 용도를 찾기 시작하면 이해하기가 더 쉬워집니다.


5

이해하기 어려운 이유는 어려운 개념 때문 아니라 구문이 일관성이 없기 때문 입니다.

   int *mypointer;

먼저 변수 작성의 가장 왼쪽 부분이 변수의 유형을 정의한다는 것을 배웁니다. C 및 C ++에서는 포인터 선언이 이와 같이 작동하지 않습니다. 대신 그들은 변수가 왼쪽의 유형을 가리키고 있다고 말합니다. 이 경우 *mypointer int를 가리키고 있습니다.

C #에서 안전하지 않은 포인터를 사용하려고 할 때까지 포인터를 완전히 파악하지 못했습니다. 정확하고 동일한 방식으로 논리적이고 일관된 구문으로 작동합니다. 포인터는 유형 자체입니다. 여기서 mypointer int에 대한 포인터입니다.

  int* mypointer;

함수 포인터를 시작하지 않아도됩니다 ...


2
실제로, 두 조각 모두 유효한 C입니다. 첫 번째 조각이 더 일반적이라는 것은 수년간의 C 스타일 문제입니다. 예를 들어 두 번째는 C ++에서 꽤 일반적입니다.
RBerteig 2009

1
두 번째 조각은 더 복잡한 선언에서는 제대로 작동하지 않습니다. 포인터 선언의 오른쪽 부분이 왼쪽에 원자 유형 지정자를 갖는 유형을 얻기 위해 포인터에 수행해야 할 작업을 표시한다는 것을 알게되면 구문이 "일관되지 않음"이 아닙니다.
Bernd Jendrissek

2
int *p;간단한 의미가 있습니다 : *p정수입니다. int *p, **pp의미 *p하고 **pp정수입니다.
Miles Rout

@MilesRout : 그러나 그것은 정확히 문제입니다. *p하고 **pp있습니다 하지 당신이 초기화되지 않기 때문에, 정수 p또는 pp또는 *pp아무것도 지점. 나는 왜 일부 사람들이 문법을 고수하는 것을 선호하는지 이해합니다. 특히 일부 경우와 복잡한 경우에는 그렇게해야합니다 (그러나 여전히 알고있는 모든 경우에 그 문제를 해결할 수는 있습니다) ... 그러나 나는 올바른 조정을 가르치는 것이 초보자들에게 오도한다는 사실보다 그 경우가 더 중요하다고 생각하지 않습니다. 못생긴 종류는 말할 것도 없습니다! :)
궤도에서 가벼움 레이스

@LightnessRacesinOrbit 티칭 오른쪽 정렬은 오도되지 않습니다. 그것을 가르치는 유일한 올바른 방법입니다. 그것을 가르치지 않으면 오도됩니다.
Miles Rout

5

C ++ 만 알았을 때 포인터로 작업 할 수 있습니다. 어떤 경우에는해야 할 일과 시행 착오에서해야 할 일이 무엇인지 알고있었습니다. 그러나 나를 완전히 이해하게 된 것은 어셈블리 언어입니다. 작성한 어셈블리 언어 프로그램을 사용하여 심각한 명령어 레벨 디버깅을 수행하는 경우 많은 것을 이해할 수 있어야합니다.


4

나는 집 주소 비유를 좋아하지만 항상 주소가 사서함 자체에 있다고 생각했습니다. 이렇게하면 포인터 참조 해제 (사서함 열기)의 개념을 시각화 할 수 있습니다.

예를 들어 링크 된 목록을 따르는 경우 : 1) 주소가있는 용지로 시작 2) 용지의 주소로 이동 3) 다음 주소가있는 새 용지를 찾으려면 우편함을 엽니 다.

선형 연결 목록에서 마지막 사서함에는 목록이 없습니다 (목록 끝). 순환 연결 목록에서 마지막 사서함에는 첫 번째 사서함의 주소가 있습니다.

3 단계는 역 참조가 발생하는 위치이며 주소가 유효하지 않은 경우 충돌하거나 잘못 될 수 있습니다. 잘못된 주소의 사서함으로 걸어 갈 수 있다고 가정하면 블랙홀이나 그 안에 세상을 뒤집는 무언가가 있다고 상상해보십시오. :)


메일 박스-번호 비유의 복잡한 문제는 Dennis Ritchie가 개발 한 언어가 바이트의 주소와 해당 바이트에 저장된 값으로 동작을 정의하지만 C 표준에 의해 정의 된 언어는 동작을 사용하도록 "최적화"구현을 초대한다는 것입니다. 모델은 더 복잡하지만 모호하고 모순되며 불완전한 방식으로 모델의 다양한 측면을 정의합니다.
supercat

3

사람들이 어려움을 겪는 주된 이유는 일반적으로 흥미롭고 매력적인 방식으로 가르치지 않기 때문이라고 생각합니다. 나는 강사가 군중으로부터 10 명의 자원 봉사자를 얻고 그들에게 각각 1 미터의 통치자를주고, 특정 구성으로 서서 서로를 가리 키도록합니다. 그런 다음 사람들을 움직여서 (그리고 통치자를 가리키는 위치) 포인터 산술을 보여주십시오. 그것은 역학에 너무 얽매이지 않고 개념을 보여주는 간단하지만 효과적 (그리고 무엇보다도 기억에 남는) 방법 일 것입니다.

C와 C ++에 도달하면 일부 사람들에게는 더 힘들어 보입니다. 이것이 그들이 실제로 제대로 이해하지 못하거나 포인터 조작이 본질적으로 그 언어에서 어렵다는 이론을 제시하고 있기 때문에 확실하지 않습니다. 나는 내 자신의 전환을 잘 기억하지 못하지만 파스칼의 포인터를 알고 C로 이동하여 완전히 잃어 버렸습니다.


2

포인터 자체가 혼란 스럽다고 생각하지 않습니다. 대부분의 사람들은 개념을 이해할 수 있습니다. 이제 얼마나 많은 포인터를 생각할 수 있습니까? 또는 얼마나 많은 수준의 간접적 인 지시에 익숙하십니까? 사람들을 우위에 두는 데 너무 많은 시간이 걸리지 않습니다. 프로그램의 버그로 인해 실수로 변경 될 수 있다는 사실은 코드에서 문제가 발생했을 때 디버그하기가 매우 어려울 수 있습니다.


2

실제로 구문 문제 일 수 있습니다. 포인터의 C / C ++ 구문은 일관성이없고 필요 이상으로 복잡해 보입니다.

아이러니하게도, 실제로 포인터를 이해하는 데 도움이 된 것은 C ++ 표준 템플릿 라이브러리 에서 반복기의 개념에 직면했다 . 반복자가 포인터의 일반화로 생각되었다고 가정 할 수 있기 때문에 역설적입니다.

때로는 나무를 무시하는 법을 배울 때까지 숲을 볼 수 없습니다.


문제는 대부분 C 선언 구문에 있습니다. 그러나 경우 포인터 사용은 확실히 쉬울 것 (*p)이었을 것입니다 (p->), 따라서 우리는이 것 p->->x대신 모호한의*p->x
MSalters

@MSalters 오 세상에, 농담이지? 불일치가 없습니다. a->b단순히 의미 (*a).b합니다.
Miles Rout

@Miles : 실제로, 그 로직 * p->x수단 * ((*a).b)반면 *p -> x수단 (*(*p)) -> x. 접두사와 접미사 연산자를 혼합하면 모호한 구문 분석이 발생합니다.
MSalters 2016 년

공백은 관련이 없기 때문에 @MSalters no. 그것은 1+2 * 39이어야한다고 말하는 것과 같습니다.
Miles Rout

2

혼란은 "포인터"개념으로 함께 혼합 된 다중 추상화 계층에서 비롯됩니다. 프로그래머는 Java / Python에서 일반적인 참조로 혼동하지 않지만 포인터는 기본 메모리 아키텍처의 특성을 노출한다는 점에서 다릅니다.

추상화 계층을 깨끗하게 분리하는 것이 좋은 원칙이며 포인터는 그렇게하지 않습니다.


1
흥미로운 점은 C 포인터가 실제로 기본 메모리 아키텍처의 특징을 드러내지 않는다는 것입니다. Java 참조와 C 포인터의 유일한 차이점은 포인터와 관련된 복잡한 유형 (예 : int *** 또는 char * ( ) (void * ))을 가질 수 있다는 것입니다. 배열에 대한 포인터 산술과 구조체 멤버에 대한 포인터가 있습니다. void * 및 배열 / 포인터 이중성. 그 외에는 똑같이 작동합니다.
jpalecek 2016 년

좋은 지적. 포인터 연산 및 버퍼 오버플로 가능성-현재 관련된 메모리 영역을 벗어남으로써 추상화를 벗어날 수 있습니다.
Joshua Fox

@ jpalecek : 기본 아키텍처 측면에서 동작을 문서화하는 구현에서 포인터가 작동하는 방식을 이해하는 것은 매우 쉽습니다. 말하는 foo[i]것은 특정 지점으로 가고 특정 거리를 앞으로 나아가고 거기에 무엇이 있는지 보는 것을 의미합니다. 문제를 복잡하게 만드는 것은 컴파일러의 이익을 위해 순수하게 표준에 의해 추가 된 훨씬 더 복잡한 추가 추상화 계층이지만 프로그래머 요구와 컴파일러 요구에 적합하지 않은 방식으로 모델링합니다.
supercat

2

내가 설명하는 방식은 배열과 색인의 관점에서였습니다. 사람들은 포인터에 익숙하지 않을 수도 있지만 일반적으로 색인이 무엇인지 알고 있습니다.

따라서 RAM이 배열이라고 가정하십시오 (및 10 바이트의 RAM 만 있음).

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

그런 다음 변수에 대한 포인터는 실제로 RAM에서 해당 변수의 첫 번째 바이트입니다.

따라서 pointer / index가있는 경우 unsigned char index = 2값은 분명히 세 번째 요소 또는 숫자 4입니다. 포인터에 대한 포인터는 해당 숫자를 가져 와서 인덱스 자체로 사용하는 위치입니다RAM[RAM[index]] 입니다.

나는 종이 목록에 배열을 그리고 같은 메모리를 가리키는 많은 포인터, 포인터 산술, 포인터 포인터 등과 같은 것들을 보여주기 위해 그것을 사용합니다.


1

우체국 박스 번호.

다른 정보에 액세스 할 수있는 정보입니다.

(우체국 사서함 번호에서 산술을 수행하면 문자가 잘못된 상자에 들어가기 때문에 문제가 발생할 수 있습니다. 누군가가 전달 주소가없는 다른 상태로 이동하면 매달린 포인터가 있습니다. 반면 우체국에서 메일을 전달하면 포인터에 대한 포인터가 있습니다.)


1

반복자를 통해 그것을 잡는 나쁜 방법은 아니지만 .. 계속해서 알다시피 Alexandrescu가 그들에 대해 불평하기 시작할 것입니다.

많은 ex-C ++ 개발자 (이터레이터가 언어를 덤프하기 전에 현대적인 포인터라는 것을 결코 이해하지 못했던)는 C #으로 이동하여 여전히 알맞은 반복자가 있다고 생각합니다.

흠, 문제는 모든 반복자가 런타임 플랫폼 (Java / CLR)이 달성하려고하는 것, 즉 새롭고 간단한 모든 사람 사용법과 완전히 일치하지 않는다는 것입니다. 어느 것이 좋을지 모르지만 그들은 보라색 책에서 한 번 말했고 C 전과 전에도 말했습니다.

우회.

매우 강력한 개념이지만 항상 그렇게한다면 결코 그렇지 않습니다. 반복자는 알고리즘의 추상화, 또 다른 예를 돕는 데 유용합니다. 그리고 컴파일 타임은 매우 간단한 알고리즘의 장소입니다. 코드 + 데이터 또는 다른 언어 C #을 알고 있습니다.

IEnumerable + LINQ + Massive Framework = 300MB의 런타임 페널티 간접 처리로 간접 참조 형식의 인스턴스를 통해 앱을 드래그합니다.

"Le Pointer는 싸다."


4
이것은 무엇과 관련이 있습니까?
Neil Williams

... "정적 연결이 가장 좋은 것"과 "내가 이전에 배운 것과 다른 점을 이해하지 못한다"는 말 외에는 무엇을 말하려고합니까?
Luaan

루안, 2000 년에 JIT를 분해하여 무엇을 배울 수 있는지 알 수 없었습니까? ASM에서 2000 년 온라인에 표시된 것처럼 포인터 테이블에서 점프 테이블로 끝나므로 다른 것을 이해하지 못하면 다른 의미를 가질 수 있습니다.주의 깊게 읽는 것이 필수적인 기술입니다. 다시 시도하십시오.
rama-jka toti

1

위의 답변 중 일부는 "포인터가 정말 어렵지 않다"고 주장했지만 "포인터가 어려운 곳"을 직접 다루지는 않았습니다. 에서 오는. 몇 년 전 저는 CS 학생들에게 첫 해를 가르쳤으며 (1 년 동안 분명히 빨 랐기 때문에) 포인터에 대한 아이디어 가 어렵지 않다는 것이 분명했습니다 . 어려운 이유는 포인터를 원하는 이유와시기를 이해 하는 것입니다. 입니다.

포인터를 사용해야하는 이유와시기에 대해 더 넓은 소프트웨어 엔지니어링 문제를 설명하는 것으로부터이 질문을 이혼 할 수 있다고 생각하지 않습니다. 모든 변수가 전역 변수가 아니 어야 하는 이유와 비슷한 코드를 함수에 포함시켜야하는 이유 는 무엇입니까 (즉, 포인터 를 사용 하여 콜 사이트에 대한 동작을 특수화).


0

포인터에 대해 너무 혼란스러워하는 것을 보지 못했습니다. 메모리의 위치, 즉 메모리 주소를 저장합니다. C / C ++에서는 포인터가 가리키는 유형을 지정할 수 있습니다. 예를 들면 다음과 같습니다.

int* my_int_pointer;

my_int_pointer에 int가 포함 된 위치의 주소가 포함되어 있다고합니다.

포인터의 문제점은 메모리의 위치를 ​​가리 키므로 사용자가 포함하지 않아야 할 위치로 쉽게 넘어갈 수 있다는 것입니다. 버퍼 오버플로에서 C / C ++ 응용 프로그램의 수많은 보안 허점을 살펴보십시오 (포인터 증가). 할당 된 경계를지나).


0

일을 좀 더 혼동시키기 위해 때로는 포인터 대신 핸들로 작업해야합니다. 핸들은 포인터에 대한 포인터이므로 백엔드는 메모리에서 항목을 이동하여 힙 조각 모음을 수행 할 수 있습니다. 포인터가 중간에 바뀌면 결과를 예측할 수 없으므로 먼저 핸들을 잠 가서 아무데도 이동하지 않도록해야합니다.

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 나보다 좀 더 일관되게 이야기합니다. :-)


-1 : 핸들이 포인터를 가리키는 포인터가 아닙니다. 어떤 의미에서든 포인터가 아닙니다. 혼동하지 마십시오.
Ian Goldby

"그들은 어떤 의미에서도 포인터가 아닙니다"-음, 나는달라고 간청합니다.
SarekOfVulcan

포인터는 메모리 위치입니다. 핸들은 고유 식별자입니다. 포인터 일 수도 있지만 배열의 인덱스 또는 그 문제에 대한 다른 것일 수도 있습니다. 당신이 준 링크는 핸들이 포인터 인 특별한 경우이지만 반드시 그럴 필요는 없습니다. 참조 parashift.com/c++-faq-lite/references.html#faq-8.8
이안 Goldby

이 링크는 어떤 의미로도 포인터가 아니라는 주장을지지하지 않습니다. "예를 들어, 핸들은 Fred ** 일 수 있습니다. Fred * 포인터는 ..." -1은 공정했다.
SarekOfVulcan

0

모든 C / C ++ 초보자에게는 동일한 문제가 있으며 "포인터를 배우기가 어렵 기"때문이 아니라 "누가 어떻게 그리고 어떻게 설명해야하는지"때문에 문제가 발생합니다. 일부 학습자는 시각적으로 일부를 시각적으로 수집하고이를 설명하는 가장 좋은 방법은 "트레이닝"예제 를 사용하는 것입니다 (언어 및 시각적 예에 적합)를 사용하는 것입니다.

여기서 "기관차" 는 아무것도 보유 할 수없는 포인터 이고 "왜건" 은 "기관차"가 당기려고하는 것입니다. 그런 다음 "수레"자체를 분류하고 동물, 식물 또는 사람 (또는 이들의 혼합)을 보유 할 수 있습니까?

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