itsource

포인터를 이해하기 위한 장벽은 무엇이며, 이를 극복하기 위해 무엇을 할 수 있을까요?

mycopycode 2022. 7. 17. 00:13
반응형

포인터를 이해하기 위한 장벽은 무엇이며, 이를 극복하기 위해 무엇을 할 수 있을까요?

왜 포인터들은 C나 C++의 많은 새로운, 심지어 오래된 대학생들에게 혼란의 주요 요소일까?포인터가 변수, 함수 및 수준을 넘어서는 방식으로 작동하는 방법을 이해하는 데 도움이 되는 도구 또는 사고 프로세스가 있습니까?

"아하, 알았어"라는 수준에 도달하기 위해 할 수 있는 좋은 연습 방법은 무엇일까요? 전체적인 개념에 얽매이지 않고 말이죠.기본적으로 시나리오와 같은 드릴을 사용합니다.

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

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

아래에 델파이 코드를 몇 개 추가하고, 필요에 따라 코멘트를 추가했습니다.다른 메인 프로그래밍 언어인 C#은 메모리 누전 같은 것을 나타내지 않기 때문에 델파이를 선택했습니다.

포인터의 개략적인 개념만을 학습하는 경우는, 아래의 설명에 기재되어 있는 「메모리 레이아웃」이라고 하는 부분은 무시해 주세요.동작 후의 메모리의 예를 나타내려고 하고 있습니다만, 실제로는 저레벨입니다.다만, 버퍼 오버런이 실제로 어떻게 기능하는지를 정확하게 설명하기 위해서, 이러한 그림을 추가하는 것이 중요했습니다.

면책사항:모든 면에서 이 설명과 메모리 레이아웃 예는 매우 단순합니다.메모리를 저레벨로 취급할 필요가 있는 경우는, 보다 많은 오버헤드와 상세 정보가 필요합니다.그러나 메모리와 포인터를 설명하는 데 있어서는 충분히 정확합니다.


다음에 사용되는 THouse 클래스는 다음과 같습니다.

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

하우스 객체를 초기화하면 생성자에 지정된 이름이 개인 필드 FName에 복사됩니다.이것이 고정 크기 배열로 정의되는 데는 이유가 있습니다.

참고로 주택 할당에 따른 오버헤드가 있습니다.아래에 예를 제시하겠습니다.

--[tttNNNNNNNNN]---^   ^|   || + - FName 어레이|+- 오버헤드

"ttt" 영역은 오버헤드이며, 일반적으로 8바이트 또는 12바이트와 같은 다양한 유형의 런타임 및 언어에 대해 더 많은 오버헤드가 발생합니다.이 영역에 저장되어 있는 값은 메모리 할당기 또는 코어 시스템 루틴 이외의 것으로 변경되지 않도록 해야 합니다.변경하지 않으면 프로그램이 크래시될 위험이 있습니다.


메모리 할당

사업가를 불러 집을 짓고, 집 주소를 알려 주세요.실제와 달리 메모리 할당은 할당 장소를 지정할 수 없지만 충분한 공간이 있는 적절한 장소를 찾아 할당된 메모리에 주소를 보고합니다.

다시 말해, 사업가는 그 자리를 선택할 것이다.

THouse.Create('My house');

메모리 레이아웃:

--[tttNNNNNNNNN]---1234 우리집

주소를 사용하여 변수 유지

새 집 주소를 종이에 써라.이 신문은 당신의 집에 대한 참고가 될 것입니다.이 종이가 없으면 길을 잃고 집을 찾을 수 없어요. 이미 안에 있지 않으면요.

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

메모리 레이아웃:

hv--[tttNNNNNNNNN]---1234 우리집

포인터 값 복사

새 종이에 주소를 쓰세요.이제 당신은 두 개의 분리된 집이 아닌 같은 집으로 갈 수 있는 두 장의 종이를 갖게 되었다.한 쪽지의 주소를 따라가서 그 집의 가구를 재배치하려고 하면, 다른 쪽 집이 실제로 한 집이라는 것을 명확하게 알 수 없는 한, 다른 쪽 집이 같은 방식으로 개조된 처럼 보이게 될 것입니다.

주의: 이것은 보통 사람들에게 설명하는 데 가장 문제가 있는 개념입니다. 두 개의 포인터는 두 개의 객체나 메모리 블록을 의미하지 않습니다.

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

메모리 해방

집을 부수다.그 후, 필요에 따라서, 용지를 재이용해 새로운 주소를 지정하거나, 이미 존재하지 않는 집의 주소를 잊어버릴 수 있습니다.

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

여기서 먼저 집을 짓고 주소를 파악한다.그리고 집에 뭔가를 하고(독자를 위한 연습으로 남겨진... 코드를 사용), 그리고 나서 집을 해방시킨다.마지막으로 변수에서 주소를 지웁니다.

메모리 레이아웃:

h <--+v +- 프리 전--[tttNNNNNNNNN]--- |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 +- 프리 전--[tttNNNNNNNNN]--- |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 어디론가. 됩니다.이 암호 이후로는 그 집에 도달할 방법이 없어져 버리게 될 거야.닫힐 되어 있으며, 이합니다.

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

hv--[tttNNNNNNNNN]---1234 우리집

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

hv---[tttNNNNNNN]---[tttNNNNNNNNN]1234 내 집 5678 내 집

이 방법을 사용하는 보다 일반적인 방법은 위와 같이 덮어쓰는 대신 무언가를 해방하는 것을 잊어버리는 것입니다.Dellphi 용어로 이 문제는 다음과 같은 방법으로 발생합니다.

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 +---[tttNNNNNNNNN]--- |1234 내 집 <--+
h(지금은 포인트 없음) <--++- 포인터를 잃은 후--[tttNNNNNNNNN]--- |1234 내 집 <--+

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


메모리를 비우되 참조를 유지하는 중(현재 유효하지 않음)하고 있습니다.

집을 부수고, 종이 한 장을 지우고, 오래된 주소가 적혀 있는 다른 종이 한 장도 가지고 있습니다.주소에 가면 집은 없지만, 폐허와 비슷한 것을 찾을 수 있을지도 모릅니다.

아마도 당신은 심지어 집을 찾을지도 모르지만, 그것은 당신이 원래 주소를 받은 집이 아니기 때문에, 그 집이 당신의 것인 것처럼 사용하려고 하면 끔찍하게 실패할 수도 있다.

때때로 당신은 이웃 주소가 세 개의 주소를 차지하는 꽤 큰 집을 가지고 있고(메인 스트리트 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

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

h1v-----------------------------------------------------------------------------------------------------------5678 우리집

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

h2 h1v v--[tttNNNNNNN]----[tttNNNNNNNNN]1234 나의 다른 집 어디론가^---+--^|+- 덮어쓰기

크래시의 원인이 되는 것은, 보존한 데이터의 중요한 부분을 덮어쓰는 경우입니다.이 부분은 랜덤하게 변경되어서는 안 됩니다.예를 들어 h1-house 이름의 일부가 프로그램 크래시 측면에서 변경된 것은 문제가 되지 않지만 오브젝트의 오버헤드를 덮어쓰는 것은 오브젝트 내의 다른 오브젝트에 저장된 링크를 덮어쓰는 것과 마찬가지로 파손된 오브젝트를 사용하려고 할 때 크래시 될 가능성이 높습니다.


링크 리스트

종이에 적힌 주소를 따라가면 집에 도착하는데, 그 집에는 새 주소가 적힌 종이가 하나 더 있습니다. 체인 안에 있는 다음 집을 위해 말이죠.

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;

메모리 레이아웃(Next House가 오브젝트 내 링크로 추가되어 있습니다.다음 그림에 4개의 LLLL이 표시되어 있습니다.

h1 h2v v--[tttNNNNNNNNLL]----[tttNNNNNNNNLL]1234홈 + 5678 캐빈 +|        ^              |+--------+ * (링크 없음)

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

메모리 주소는 기본 용어로 숫자에 불과합니다.메모리를 바이트의 큰 배열로 생각할 경우 첫 번째 바이트는 주소 0, 다음 바이트는 주소 1이 됩니다.이것은 간단하지만, 충분합니다.

이 메모리 레이아웃은 다음과 같습니다.

h1 h2v v---[tttNNNNNNN]---[tttNNNNNNNNN]1234 내 집 5678 내 집

다음의 2 개의 주소(맨 왼쪽 - 는 주소 0)를 가질 수 있습니다.

  • h1 = 4
  • h2 = 23

즉, 위의 링크 리스트는 모두 다음과 같습니다.

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

통상, 「nowhere」를 포인트 하는 주소는 제로 주소로서 격납됩니다.


기본 용어로 포인터가 뭐죠?

포인터는 메모리 주소를 유지하는 변수입니다.일반적으로 프로그래밍 언어에 번호를 지정하도록 요청할 수 있지만, 대부분의 프로그래밍 언어와 런타임은 숫자 자체가 아무런 의미가 없다는 이유만으로 아래에 숫자가 있다는 사실을 숨기려고 합니다.포인터를 블랙박스로 생각하는 것이 가장 좋습니다.실제로 어떻게 구현되는지 알지도, 신경도 쓰지 않을 것입니다.

포인터를 설명하는 데 도움이 되는 유추는 하이퍼링크입니다.대부분의 사람들은 웹 페이지 상의 링크가 인터넷 상의 다른 페이지를 '포인트'하는 것을 이해할 수 있으며, 하이퍼링크를 복사하여 붙여넣을 수 있다면 두 사람 모두 동일한 원본 웹 페이지를 가리킬 수 있습니다.원래 페이지를 편집하고 링크(포인터) 중 하나를 따라가면 새로 업데이트된 페이지가 나타납니다.

나의 첫 Comp Sci 수업에서 우리는 다음과 같은 연습을 했다.네, 200명 정도의 학생이 있는 강의실이었는데...

.int john;

존이 일어서다

은 이렇게 쓰고 있습니다.int *sally = &john;

샐리는 일어서서 존을 가리켰다.

수::int *bill = sally;

빌이 일어서서 존을 가리키다

수::int sam;

샘이 일어서다

수::bill = &sam;

빌은 이제 샘을 가리켰다.

감이 잡히실 겁니다포인터 할당의 기본을 배울 때까지 이걸 하는 데 한 시간 정도 걸린 것 같아요.

포인터가 그렇게 많은 사람들을 혼란스럽게 하는 이유는 포인터가 컴퓨터 아키텍처에 대한 경험이 거의 없거나 전혀 없기 때문이다.컴퓨터(기계)가 실제로 어떻게 구현되는지 모르는 사람이 많기 때문에 C/C++에서 일하는 것은 낯선 것 같습니다.

훈련에서는 포인터 조작(로드, 저장, 직접/간접 주소 지정)에 초점을 맞춘 명령 세트를 사용하여 간단한 바이트 코드 기반 가상 머신(선택한 모든 언어로 파이썬이 이 작업에 적합합니다)을 구현하도록 요청합니다.그런 다음 해당 명령 집합에 대한 간단한 프로그램을 작성하도록 요청합니다.

단순한 덧셈보다 약간 더 많은 것을 요구하는 것은 포인터를 수반하며, 그들은 그것을 얻을 수 있을 것이다.

저는 개념으로서의 포인터가 특별히 까다롭다고 생각하지 않습니다.-대부분의 학생들의 멘탈 모델은 이와 같은 것에 매핑되어 있고, 몇 가지 빠른 박스 스케치가 도움이 될 수 있습니다.

적어도 과거에 경험하고 다른 사람들이 다루는 것을 본 적이 있는 어려움은 C/C++에서의 포인터 관리가 지나치게 복잡할 수 있다는 것이다.

처음에 제가 포인터를 이해하는 데 어려움을 겪었던 이유는 많은 설명들이 참고문헌을 통과하는 것에 대한 많은 헛소리를 포함하고 있기 때문입니다.이 모든 것이 문제를 혼란스럽게 한다.포인터 파라미터를 사용하는 경우 값은 그대로 전달되지만 값은 int가 아닌 주소일 수 있습니다.

다른 사용자가 이미 이 튜토리얼에 링크했지만 포인터를 이해하기 시작한 순간을 강조 표시할 수 있습니다.

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

int puts(const char *s);

.const. 변수가 되었습니다.puts() 포인터의 값입니다(C의 모든 파라미터는 값에 의해 전달되기 때문에).또한 포인터의 값은 포인터가 가리키는 주소, 단순히 주소입니다.그래서 우리가 글을 쓸 때puts(strA);앞에서 설명한 바와 같이 strA[0]의 주소를 전달하고 있습니다.

내가 이 말을 읽는 순간 구름이 갈라지고 햇빛이 나를 포인터 이해로 감싸주었다.

네가 VB라고 해도.NET 또는 C# 개발자(나처럼)는 안전하지 않은 코드를 사용하지 않습니다. 포인터가 어떻게 작동하는지 이해할 가치가 있습니다.그렇지 않으면 오브젝트 참조가 어떻게 작동하는지 이해할 수 없습니다.그러면 오브젝트 참조를 메서드에 전달하면 오브젝트가 복사된다는 공통적인 개념도 갖게 됩니다.

포인터의 문제는 개념이 아닙니다.사형 집행과 언어입니다.선생님들이 어려운 것은 포인터의 개념이지 전문용어가 아니라고 생각하거나 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의 주소를 필요로 한다」등의 의미와 명확성을 가지고 있지 않기 때문에, 명확하지 않습니다.그 대답은 또한 "마이버퍼"가 무엇으로 시작하는지, 그리고 그것으로 무엇을 할 것인지에 달려 있습니다.이 언어는 실제로 발생하는 중첩 수준을 지원하지 않습니다.예를 들어 새로운 버퍼를 생성하는 함수에 포인터를 넘겨야 할 때 포인터가 버퍼의 새로운 위치를 가리키도록 수정됩니다.포인터를 사용할지 포인터에 대한 포인터를 사용할지 포인터의 내용을 수정하기 위해 어디로 이동해야 하는지 알 수 있습니다.대부분의 경우 저는 "점수"가 무엇을 의미하는지 추측해야 하며, 추측에 대한 경험이 얼마나 되든 상관없이 대부분의 경우 제가 틀립니다.

'포인터'는 과부하가 걸렸어요포인터는 값에 대한 주소입니까?또는 어떤 값에 대한 주소를 유지하는 변수인지도 알 수 있습니다.함수는 포인터를 필요로 할 때 포인터 변수가 보유하고 있는 주소를 원합니까, 아니면 포인터 변수에 대한 주소를 원합니까?저는 혼란스러워요.

Ted Jensen의 "Tutorial on Pointers and Arrays in C"는 포인터에 대해 학습하기 위한 훌륭한 자료입니다.10개의 레슨으로 나누어져 포인터가 무엇인지(그리고 무엇을 위한 것인지) 설명하고 기능 포인터로 끝납니다.http://web.archive.org/web/20181011221220/http://home.netcom.com:80/~tjensen/ptr/cpoint.htm

Beej's Guide to Network Programming은 Unix sockets API를 가르쳐 줍니다.이 API에서 정말 재미있는 일을 시작할 수 있습니다.http://beej.us/guide/bgnet/

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

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

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

집 주소의 유추는 좋지만, 나는 항상 주소가 우편함 그 자체라고 생각해 왔다.이렇게 하면 포인터를 참조 해제(우편함 열기)하는 개념을 시각화할 수 있습니다.

예를 들어 링크된 목록을 따르는 경우: 1) 용지에 주소가 기재되어 있는 것을 시작으로 2) 용지에 기재되어 있는 주소로 이동 3) 우편함을 열고 다음 주소가 기재되어 있는 새로운 용지를 찾습니다.

선형 링크 목록에서 마지막 편지함에 아무것도 없습니다(목록 끝).순환 링크 목록에서 마지막 우편함에는 첫 번째 우편함의 주소가 있습니다.

스텝 3은 참조 해제가 발생하는 장소이며, 주소가 무효인 경우 크래시 또는 오차가 발생하는 장소입니다.유효하지 않은 주소의 우편함에 걸어갈 수 있다고 가정할 때, 블랙홀 같은 것이 세계를 뒤엎는다고 상상해 보세요.

우체국 박스 번호.

다른 것에 접근할 수 있는 정보입니다.

(그리고 우체국 박스 번호를 계산하면, 그 문자가 잘못된 박스에 들어가 버리기 때문에 문제가 있을 수 있습니다.전송 주소가 없는 다른 상태로 이동하면 포인터가 달랑달랑글쎄요반면 - 우체국이 메일을 회송할 경우, 포인터에 대한 포인터가 있습니다.)

왜 포인터들은 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

예를 들어 몇 가지 기본이 설명되는 것은 아닐까요?

나는 사람들이 그것에 대해 어려움을 겪는 주된 이유는 일반적으로 그것이 흥미롭고 매력적으로 가르쳐지지 않기 때문이라고 생각한다.강연자가 군중으로부터 10명의 지원자를 받아 각각 1미터씩의 자를 주고, 일정한 구성으로 주위에 서게 하고, 그 자를 이용해 서로를 가리켜 주는 것을 보고 싶다.그런 다음 사람들을 이동시켜 포인터 산수를 표시합니다(그리고 그들이 눈금자를 가리키는 위치).이 방법은 단순하지만 효과적인(그리고 무엇보다 기억에 남는) 방법으로 기계학에 너무 얽매이지 않고 개념을 보여줄 수 있습니다.

일단 C와 C++에 도달하면 어떤 사람들은 더 어려워질 것 같아요.이것이 그들이 제대로 이해하지 못하는 이론을 마침내 실행에 옮겼기 때문인지 아니면 포인터 조작이 그 언어들에서 본질적으로 더 어렵기 때문인지 나는 잘 모르겠다.나 자신의 전환은 잘 기억나지 않지만 파스칼의 포인터를 알고 C로 옮겼다가 완전히 길을 잃었다.

나는 조언을 이해하는 데 있어 가장 큰 장벽은 나쁜 선생님이라고 생각한다.

거의 모든 사람이 포인터에 대한 거짓말을 배웁니다.메모리 주소에 지나지 않는 것, 또는 임의의 장소를 가리킬 수 있는 것.

그리고 물론 이해하기 어렵고 위험하고 반마법적이기도 합니다.

그 중 어느 것도 사실이 아니다.포인터는 실제로 C++ 언어가 말하는 것에 충실하고 실제로 작동하는 것으로 판명되는 속성을 주입하지 않는 한 매우 단순한 개념이지만, 그럼에도 불구하고 언어에 의해 보장되지 않으며, 포인터의 실제 개념의 일부가 아닙니다.

저는 몇 달 전에 이 블로그 투고에 이 문제에 대한 설명을 쓰려고 했습니다. 누군가에게 도움이 되었으면 합니다.

(주의해 주세요.누군가 나를 현학적이라 하기 전에, 네, C++ 규격에서는 포인터가 메모리 주소를 나타낸다고 되어 있습니다.다만, 「포인터는 메모리 주소이며, 메모리 주소 이외에는 아무것도 없고, 메모리 주소와 교환할 수 있거나 생각할 수 있다」라고 하는 것은 아닙니다.구별이 중요하다)

포인터의 복잡성은 우리가 쉽게 가르칠 수 있는 수준을 넘어선다.학생들이 서로를 가리키게 하거나 집 주소가 적힌 종이를 사용하는 것은 둘 다 훌륭한 학습 도구이다.기본적인 개념을 잘 소개하고 있습니다.사실, 기본 개념을 배우는 것은 포인터를 성공적으로 사용하기 위해 필수적이다.그러나 프로덕션 코드에서는 이러한 간단한 데모를 캡슐화할 수 있는 것보다 훨씬 더 복잡한 시나리오에 들어가는 것이 일반적입니다.

저는 다른 구조물을 가리키는 구조물이 다른 구조물을 가리키는 시스템에 관여한 적이 있습니다.그러한 구조 중 일부는 (추가 구조에 대한 지침보다는) 내재된 구조를 포함하였다.여기서 포인터가 정말 헷갈리는군요.여러 개의 간접 레벨이 있고, 다음과 같은 코드로 끝나는 경우:

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

매우 빨리 혼란스러워질 수 있습니다(더 많은 라인이 추가되어 더 많은 레벨이 될 수 있습니다.포인터 배열 및 노드 간 포인터(트리, 링크 목록)를 삽입하면 상황은 더욱 악화됩니다.그런 시스템을 만들기 시작하면 정말 좋은 개발자들이 길을 잃는 걸 봤어요. 기본을 잘 아는 개발자들도요.

포인터의 구조가 복잡하다고 해서 반드시 코딩이 나쁘다고는 할 수 없습니다.구성은 좋은 객체 지향 프로그래밍의 필수 요소이며 원시 포인터가 있는 언어에서는 불가피하게 다중 계층 간접으로 이어질 것입니다.또한 시스템은 스타일이나 기술이 서로 일치하지 않는 구조를 가진 서드파티 라이브러리를 사용해야 하는 경우가 많습니다.그런 상황에서는 당연히 복잡성이 생기기 마련입니다(하지만 가능한 한 싸워야 합니다).

나는 학생들이 포인터를 배우도록 돕기 위해 대학이 할 수 있는 최선의 일은 포인터 사용을 필요로 하는 프로젝트와 함께 좋은 데모를 사용하는 것이라고 생각한다.어려운 프로젝트 하나가 포인터를 이해하는 데 천 번의 시연보다 더 많은 도움이 될 것입니다.시연은 얕은 이해를 얻을 수 있지만, 포인터를 깊이 이해하기 위해서는 실제로 그것을 사용해야 합니다.

컴퓨터 사이언스 튜터로서 (당시에) 포인터를 설명할 때 매우 도움이 된다고 생각했기 때문에, 이 리스트에 유추해 보겠습니다.먼저 다음과 같이 하겠습니다.


스테이지를 설정합니다.

주차 공간이 3개인 주차장에 번호가 매겨집니다.

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

어떤 면에서는, 이것은 기억의 장소와 같은 것입니다.이라고 할 수 배열 같은 거죠.은 차가 있는 right right 、 array ( 、 array ( 、 array ( 。parking_lot[3] = {0}를 참조해 주세요.


데이터 추가

주차장은 오랫동안 비어있지 않다...아무 의미도 없을 거고 아무도 짓지 않을 거예요예를 들어, 주차장에서 파란색, 빨간색, 녹색 등 3대의 차가 가득 찬다고 가정해 봅시다.

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

은 모두 이기 때문에, 이것을 할 수 한 가지 은, 를 들면, 「차」)라고 하는 것입니다.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)그린카(그 장소의 데이터)가 존재하는 것을 확인합니다.

포인터를 학습하기 어려운 것은 포인터가 "이 메모리 위치에는 int, double, character 등을 나타내는 비트 집합이 있다"는 생각에 익숙해질 때까지라고 생각합니다.

포인터를 처음 봤을 때 그 메모리 위치에 무엇이 있는지 알 수 없습니다."주소가 있다니 무슨 말이야?"

나는 "당신이 그것들을 얻거나 얻지 못하거나 둘 중 하나"라는 생각에 동의하지 않는다.

실제 용도를 찾기 시작하면(예를 들어 큰 구조를 함수로 변환하지 않음) 이해하기 쉬워집니다.

C++만 알고 있으면 포인터로 작업할 수 있습니다.경우에 따라서는 어떻게 해야 할지, 시행착오를 겪지 말아야 할지를 어느 정도 알고 있었습니다.하지만 제가 완전히 이해할 수 있었던 것은 어셈블리 언어입니다.작성한 어셈블리 언어 프로그램으로 심각한 명령 수준 디버깅을 하면 많은 것을 이해할 수 있을 것입니다.

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

int *mypointer;

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

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

int* mypointer;

함수 포인터도 알려주지 마...

나는 그 조언 자체가 혼란스럽다고 생각하지 않는다.대부분의 사람들은 그 개념을 이해할 수 있다.이제 생각할 수 있는 포인터 수 또는 사용하기에 적합한 간접 수준 수는 얼마나 됩니까?사람들을 곤경에 빠뜨리는 데는 그리 많은 것이 필요하지 않다.프로그램의 버그로 인해 실수로 변경될 수 있다는 사실도 코드에 문제가 생겼을 때 버그를 디버깅하는 것을 매우 어렵게 만들 수 있습니다.

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

아이러니하게도 포인터를 이해하는 데 실제로 도움이 된 것은 c++ Standard Template Library에서 반복기라는 개념을 접한 것입니다.아이러니한 것은 반복자가 포인터의 일반화로 생각되었을 것이라고 추측할 수 있기 때문입니다.

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

이러한 혼란은 "점수" 개념에서 여러 추상화 계층이 혼합되어 있기 때문에 발생합니다.프로그래머는 Java/Python의 일반적인 참조에 혼란스러워하지 않지만 포인터는 기본 메모리 아키텍처의 특성을 드러낸다는 점에서 다릅니다.

추상화 레이어를 깨끗하게 분리하는 것은 좋은 원칙이며 포인터에서는 그렇게 하지 않습니다.

제가 설명하고자 하는 방법은 배열과 인덱스에 관한 것이었습니다. 사람들은 포인터에 익숙하지 않을 수 있지만 일반적으로 인덱스가 무엇인지 알고 있습니다.

예를 들어 RAM이 어레이라고 가정합니다(RAM은 10바이트밖에 없습니다).

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

변수에 대한 포인터는 실제로는 RAM에 있는 변수의 인덱스(첫 번째 바이트)일 뿐입니다.

인덱스가 ,unsigned char index = 2네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째, 네 번째.그입니다.RAM[RAM[index]].

종이 목록에 배열을 그려서 같은 메모리를 가리키는 많은 포인터, 포인터 산술, 포인터 투 포인터 등을 표시하기 위해 사용합니다.

반복기를 통해 이해하는 것도 나쁘지 않습니다.하지만 계속 찾아보면 알렉상드레스쿠가 그들에 대해 불평하기 시작하는 걸 볼 수 있을 거야

많은 이전 C++ 개발자들(언어를 폐기하기 전에 반복자가 현대적인 포인터라는 것을 전혀 이해하지 못한)은 C#으로 점프하여 여전히 적절한 반복자를 보유하고 있다고 믿고 있습니다.

음, 문제는 이 모든 반복기가 런타임 플랫폼(Java/CLR)이 달성하려고 하는 것, 즉 새롭고 단순한 everyone-is-a-dev 사용법에 완전히 모순된다는 것입니다.좋을 수도 있지만, 보라색 책에 한 번 써봤고, 심지어 C 이전이나 이전에도 써봤죠.

인다이렉션.

매우 강력한 컨셉이지만 끝까지 하면 절대 그렇지 않다.반복기는 알고리즘의 추상화에 도움이 되기 때문에 유용합니다.컴파일 시간은 알고리즘의 장소입니다.매우 간단합니다.코드 + 데이터 또는 다른 언어 C#:

IEnumerable + LINQ + Massive Framework = 수많은 참조 유형을 통해 300MB 런타임 패널티 유도.

"르 포인터는 싸다."

위의 몇 가지 답변은 "점수는 별로 어렵지 않다"고 주장했지만, "점수는 어렵다!"가 어디에서 나오는지는 직접적으로 언급하지 않았다.몇 년 전, 나는 1학년 CS 학생을 지도했는데(분명히 빨고 있었기 때문에) 포인터의 아이디어는 어렵지 않다는 것이 분명했다.어려운 것은 포인터가 필요한 이유와 타이밍을 이해하는 것입니다.

왜, 언제 포인터를 사용해야 하는지와 같은 질문을 더 광범위한 소프트웨어 엔지니어링 문제를 설명하는 것과 분리할 수 없다고 생각합니다.모든 변수가 글로벌 변수가 되지 않는 이유 및 유사한 코드를 함수에서 제외해야 하는 이유(즉, 포인터를 사용하여 콜사이트에 대한 동작을 특정합니다).

포인터가 뭐가 그렇게 헷갈리는지 모르겠어요.메모리내의 장소, 즉 메모리 주소를 보존하고 있는 위치를 가리킵니다.C/C++에서 포인터가 가리키는 유형을 지정할 수 있습니다.예를 들어 다음과 같습니다.

int* my_int_pointer;

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

포인터의 문제는, 포인터가 메모리내의 장소를 가리키고 있기 때문에, 있으면 안 되는 장소까지 간단하게 소거할 수 있다는 것입니다.그 증거로서 버퍼 오버플로우로부터의 C/C++ 애플리케이션의 수많은 시큐러티 홀을 확인합니다(할당된 경계를 넘어 포인터를 증가시킵니다).

조금 더 혼란스럽게 하기 위해 포인터 대신 핸들을 사용해야 하는 경우가 있습니다.핸들은 포인터에 대한 포인터이므로 백엔드가 메모리 내의 것을 이동하여 힙 조각 모음을 수행할 수 있습니다.중간에 포인터가 변경되면 결과를 예측할 수 없기 때문에 먼저 핸들을 잠그고 아무 것도 움직이지 않도록 해야 합니다.

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

모든 C/C++ 초보자들도 같은 문제를 가지고 있는데, 그 문제는 "점수가 배우기 어렵기 때문"이 아니라 "누구이고 어떻게 설명되었는가" 때문이다.일부 학습자는 이를 시각적으로 일부 구두로 수집합니다. 이를 설명하는 가장 좋은 방법은 "훈련" 예제를 사용하는 것입니다(언어 및 시각적 예제의 경우).

여기서 "locomotive"는 아무것도 잡을 수 없는 포인터이고 "wagon"은 "locomotive"가 당기는(또는 가리키는) 포인터입니다.그런 다음 "왜건" 자체를 분류할 수 있으며, 동물, 식물 또는 사람(또는 혼합)을 포함할 수 있습니다.

언급URL : https://stackoverflow.com/questions/5727/what-are-the-barriers-to-understanding-pointers-and-what-can-be-done-to-overcome

반응형