본문 바로가기

awesome-c Beginner 번역/A tutorial on pointers

<비공식 번역>awesome-c Beginner 번역 : CHAPTER 1 : What is a pointer?

현재위치


<주의!!>

'A TUTORIAL ON POINTERS AND ARRAYS IN C'의 공식적인 번역이 아니며 수를 받은 것 역시 아닙니다!!




CHAPTER 1: What is a pointer?(챕터 1: 포인터가 뭐지?)

초심자가 C에서 어렵다고 느끼는 것들중 하나가 포인터입니다. 이 튜토리얼의 목적은 포인터의 소개 그리고 초심자에게 포인터 사용을 제공하는 것입니다.


나는 초심자가 포인터에 문제를 느끼는 주요한 이유는 변수에 대한 약하거나 최소한의 느낌을 가지고 있다는 것을 발견했습니다,(그들이 C를 사용할 때). 따라서 우리는 C 변수에 대한 일반적인 논의로 시작합니다.


프로그램에서 변수는 이름을 가지는 어떤 것이며 그 값은 변화할 수 있습니다. 그 변수 값을 고정하기 위해서 complier와 linker가 처리하는 방법은 컴퓨터 안에 특정한 메모리 공간을 할당합니다.

그 블록의 사이즈는 변수가 변화시키도록 허용되는 범위에 의존합니다.(허용된 만큼 할당된다.) 예를 들어, 32bit PC의 integer 변수의 사이즈는 4byte입니다. 옛날 16bit PC에서 integer 변수는 2byte였습니다. C에서 integer 변수 사이즈는 PC유형에 동일할 필요는 없습니다. 또한 C의 정수형 변수는 더 있습니다. 우리는 C의 기본 텍스트를 읽을 수 있는 integer, long integers, short integers를 압니다. 이 문서는 4byte integer변수와 32bit 시스템 사용을 기반으로 합니다.


만약 당신이 다양한 타입의 integer변수의 사이즈를 알고 싶다면 당신의 시스템에서 그 정보를 알려줄 다음 코드를 실행해보시길 바랍니다.



우리가 변수를 선언할 때 우리는 컴파일러에게 두 가지를 명시한다. 그것은 변수의 이름과 변수의 타입이다. 예를 들어, 우리는 이름이 k인 인티저 타입 변수를 선언할 때 다음과 같이 쓴다.


int k;


이 상태의 int라고 적힌 부분은 컴파일러에게 정수의 값을 넣기 위한 4byte의 메모리를 확보하게 만든다.(각자의 PC에서) 이것은 또한 symbol table에도 적용되어 테이블에는 k라는 symbol과 4byte가 할당 됐던 메모리의 가상주소가 추가된다.


따라서 만약 우리가 다음과 같이 쓴다면:


k = 2;


이 문장이 실행 될 때 2라는 값은 k의 값을 저장하기 위해 예약했었던 메모리 위치에 위치될 것임을 예상할 수 있다. C에서 우리는 정수 k를 "개체"로서 참조하게 된다.


어떤 의미에서 이것은 k라는 개체와 관련된 두개의 "값"입니다. 하나는 저장된 정수의 값입니다. (위의 예제에 따르자면 2) 그리고 다른 하나는 메모리의 위치"값" 입니다. 즉, k의 주소입니다. 몇몇 텍스트는 명명법을 rvalue(right value, "~의 값입니다."로 읽는) 그리고 lvalue(left value, "엘 값"으로 읽는)로 하여(번역자 : 엘...현지 축약어 인가요? 모르겠습니다.) 각각 두 값을 참조합니다.


몇몇 언어에서 lvalue는 '='할당 연산자 왼쪽에 허용되는 값이다(즉 오른쪽 측면에 평가의 결과 주소). rvalue는 할당 문의 오른쪽 측면에 있는 것입니다. 위에서 2인.. Rvalue는 왼쪽 할당문에서 사용 될 수 없습니다. 따라서 2 = k는 불가능합니다.


사실은 위의 "lvalue"의 정의는 C를 위해서 약간(somewhat) 수정되었습니다. K&R II (page 197):[1]에 따르면

"개체는 저장영역에 명명되었다. ; lvalue는 개체를 참조하는 중인 표현이다."

그러나, 이 시점에서, 원래 위의 언급한 정의로 충분(sufficient)합니다. 우리가 포인터에 익숙해 질수록 우리는 더 자세한 세부사항으로 갈 것입니다.


좋습니다! 이제 생각해봅시다:


int j, k;


k = 2;

j = 7;   <-- line 1

k = j;   <-- line 2


위에서, 컴파일러는 line 1의 j를 변수 j(값)의 주소로 해석하고 7의 값이 그 주소에 복사되도록 코드를 만듭니다. 그러나 line 2에서 j는 rvalue로 해석됩니다.(이것은 할당 연산자 '='의 오른쪽 측면에 있기 때문에). 그 j는 j의 값이 저장되어 있는 메모리 주소를 참조합니다. 이 경우에는 7이겠네요. 그래서 7은 k의 lvalue로 지정된 주소로 복사됩니다.


이 모든 예제에서 우리는 한 곳의 저장된 장소에서 다른 곳으로 4byte를 복사될 때 rvalue를 복사하는 모든 것에4byte의 integer변수를 사용합니다 (번역자 : 이게 무슨 뜻일까요... 흠)

(In all of these examples, we are using 4 byte integers so all copying of rvalues from one storage location to the other is done by copying 4 bytes.)

2 byte integer를 사용할 때 우리는 2 byte를 복사할 것입니다.


이제, 우리는 lvalue라는 설계된 변수를 원하는 이유가 있다고 가정해봅시다. 이러한 값을 보관하는데 필요한 크기는 시스템에 의존합니다. 전체 메모리가 64K 이던 옛날 컴퓨터에서, 메모리의 임의 지점의 주소는 2byte에 포함됐었습니다. 더 많은 메모리가 있는 컴퓨터는 주소를 유지하기위해 더 많은 byte를 요구합니다. 필요한 실제 크기는 너무 중요하진 않습니다. 그래서 컴파일러에게 우리가 저장하고자하는 주소를 컴파일러에게 알리는 방법이 있습니다.


그러한 변수를 포인터 변수라고 부릅니다.(희망적인 이유는 곧 명확하게 될 겁니다.) C에서 우리가 포인터를 정의 할 때 우리는 선행하여 그 이름 앞에 별표(asterisk)를 사용합니다. 또한 C에서 우리는 포인터에게 타입을 부여합니다. 이 경우에는 우리가 포인터를 저장할 주소에 저장된 데이터 타입을 참조합니다. 예를 들어 변수 선언을 고려해 봅시다.


int *ptr;


ptr은 우리 변수의 이름입니다.(k는 그저 integer변수의 이름입니다.) '*'은 컴파일러에게 우리가 포인터 변수를 사용할 것임을 알립니다. 그러나 세팅의 측면에서 메모리에 주소를 저장하는데에 많은 byte가 요구된다. int는 우리가 선언한 포인터 변수가 integer의 주소를 저장하는데 사용할 것임을 의도합니다. 그러한 포인터는 "점"에 정수로 언급된다.(번역자 : 무슨말일까요) 그러나 우리가 int k;라고 썼을때 우리는 k의 값을 준 것은 아닙니다. 만약 이 정의가 ANSI 호환 컴파일러의 함수로 관계될 경우 0의 값으로 초기화 될 것입니다. 비슷하게, ptr은 아무값도 가지지 않았습니다. 우리는 위의 정의에서 아무런 주소를 저장하지 않았습니다. 이 경우, 다시 어떤 함수에 의한 정의에 관계 된다면 이것은 C 개체 혹은 함수가 보장하는대로 초기화 됩니다.(번역자 : 컴파일러에 따라 초기화가 다르다는 것 같습니다.) 이러한 방법으로 초기화된 포인터를 "null" 포인터라고 부릅니다.


코드가 개발된 시스템에 의존적하여 null 포인터에 적용된 실제 비트 패턴은 0으로 측정되지 않을 수 있습니다. 다양한 시스템에 다양한 컴파일러에서 호환되는 코드를 만들려면, 매크로는 null 포인터를 나타내는데에 사용됩니다. 그 매크로는 NULL이란 이름을 가집니다. 따라서 포인터 값 설정에는 NULL 매크로가 사용되고, 할당문에서 ptr = NULL로 쓰임, 포인터가 null 포인터로 되었음을 보장한다. 비슷하게, if(k ==0) 에서 0의 integer값을 테스트 할 수 있고 null 포인터를 if(ptr == NULL)을 사용함으로써 테스트 할 수 있다.


그러나 다시 우리는 새로운 변수 ptr을 사용합니다. 우리가 ptr에 integer 변수 k의 주소를 저장하길 원한다고 가정합니다. 우리가 이를 사용하기 위해서 단항 & 연산자를 사용한다 :


ptr = &k;


& 연산자 하는 것은 k의 lvalue(주소)를 회수하는 것이다, 비록 k가 '='의 오른쪽 할당 연산자에 있더라도, 그리고 우리 포인터 ptr의 내용을 복사한다. 지금 ptr은 "지점" k를 말한다. 지금 우리와 함께하는 부담(Bear with us now), 그것은 우리가 더 논의해야할 연산자의 하나이다.


"dereference(포인터가 가리키는 곳에 저장된 데이터에 접근) 연산자"은 별표이며 이것은 다음과 같이 사용한다. :


*ptr = 7;


유사하게, 우리는 이렇게 쓸 수 있습니다:


printf("%d\n", *ptr);


ptr에 있는 주소에 저장된 integer 값을 화면에 띄우는 것.


한 가지 방법은 다음 프로그램을 실행하고 코드와 출력을 조심스럽게 검토하여 함께 맞는 방법을 보는 것입니다.

(번역자 : 프로그램을 실행하고 코드와 출력을 보고 검토하는 것입니다. 가 이해하기 쉬운 표현인 것 같습니다.)


------------ Program 1.1 --------------------------------- 


/* Program 1.1 from PTRTUT10.TXT   6/10/97 */


#include <stdio.h>


int j, k;

int *ptr;


int main(void)

{

    j = 1;

    k = 2;

    ptr = &k;

    printf("\n");

    printf("j has the value %d and is stored at %p\n", j, (void *)&j);

    printf("k has the value %d and is stored at %p\n", k, (void *)&k);

    printf("ptr has the value %p and is stored at %p\n", ptr, (void *)&ptr);

    printf("The value of the integer pointed to by ptr is %d\n", *ptr);


    return 0;

}


메모 : 우리는 C의 이러한 측면을 논의하기 위해 여기에 사용된 (void *) 표현을 사용이 필요하다. 지금, 당신의 테스트 코드에 포함되어 있다. 우리는 나중에 이것을 설명해줄 것입니다.


분석:


  - 변수는 종류와 이름을 지정하여 선언하였다. (예 : int k;)

  - 포인터 변수는 별표가 컴파일러에게 이 변수의 이름이 ptr이고 포인터 변수임을 알려준다.(이 경우 정수)(예 : int *ptr)

  - 한번 변수로 선언되면 &k처럼 우리는 단항 & 연산자를를 이름과 함께 사용하여 주소를 얻을 수 있다.

  - 우리는 포인터로 "dereference(포인터가 가리키는 곳에 저장된 데이터에 접근)"할 수 있다. 즉, *ptr의 단항 '*' 연산자를 사용하여 가리키는 값을 참조한다.

  - 변수의 "lvalue(좌변)"은 메모리에 저장되는 값이다. 변수의 rvalue는 (그 주소) 해당 변수에 저장된 값이다.


제 1 장 참조 : 

"The C Programming Language" 2nd Edition

B. Kernighan and D. Ritchie

Prentice Hall

ISBN 0-13-110362-8

 

출처1 : https://github.com/aleksandar-todorovic/awesome-c

출처2 : http://home.netcom.com/~tjensen/ptr/pointers.htm