본문 바로가기

awesome-c Beginner 번역/A tutorial on pointers

<비공식 번역>awesome-c Beginner 번역 : CHAPTER 3 : Pointers and Strings

현재위치


<주의!!>

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




CAHPTER 3 : Pointers and Strings (챕터3 : 포인터와 문자열)


string에 대한 학습은 포인터와 배열사이의 관계묶어서 이해하는데에 유용합니다. 또한 C string 함수의 일부를 예시하기가 쉽습니다. 결국 포인터를 어떻게, 언제 함수에 전달되어야 하는지 설명합니다.


C에서, string은 문자들의 배열입니다. 다른 언어에서도 통용되는 사실은 아닙니다.. BASIC, Pascal, Fortran 그리고 다양한 언어에서 string은 데이터 타입입니다. 그러나 C에서는 그렇지 않습니다. C에서 string은 마지막이 byte가 0이란 문자로 끝이나는 문자들의 배열입니다('\0'라고 씁니다.). 토의를 하기에 앞서서 실제프로그램에서는 쓰지 않을테지만 약간의 코드를 작성하겠습니다. 예를 보시죠:


char my_string[40];


my_string[0] = 'T';

my_string[1] = 'e';

my_string[2] = 'd';

my_string[3] = '\0';


이런식으로 string을 만드는 사람은 없겠지만 string은 이와같이 nul 문자로 끝이나는 문자의 배열입니다. C의 정의에 의해서 string은 nul문자로 끝이나는 문자배열입니다. 주의하세요 "nul"은 "NULL"과 같지 않습니다. nul은 escape sequence '\0'에 의한 정의 0과 관련이 있습니다. 즉, 메모리의 1byte를 차지합니다. 반대로 NULL은 null포인터를 초기화하는데 사용하는 매크로의 이름입니다. NULL은 여러분의 C 컴파일러 내부 헤더파일의 #define으로 정의하고 nul은 전혀 #define으로 정의하지 않을 수 있습니다.


위의 코드를 쓰는것은 너무 많은 시간을 사용할 것이기 때문에, C 동일한 결과를 내는 대체할 수 있는 두가지 방식을 허용합니다. 첫번째 방식은 이렇게 씁니다:


char my_string[40] = {'T', 'e', 'd', '\0', };


그러나 C가 허용하는 보다 더 편한 방법이 있습니다.


char my_string[40] = "Ted";


먼저 등장한 예시에서 쓴 작은 따옴표 대신 큰 따옴표를 쓸 때 nul 문자 ( '\0' )는 자동으로 추가 문자열의 끝에 추가됩니다.


위의 모든 케이스는 동일한 결과를 만들어냅니다. 컴파일러는 40byte의 메모리 블록을 문자를 저장하기위해 확보하고 처음 4개의 문자를 Ted\0로 초기화합니다.


자 이제 다음 프로그램을 생각해봅시다.

------------------program 3.1-------------------------------------


/* Program 3.1 from PTRTUT10.HTM   6/13/97 */


#include <stdio.h>


char strA[80] = "A string to be used for demonstration purposes";

char strB[80];


int main(void)

{


    char *pA;     /* a pointer to type character */

    char *pB;     /* another pointer to type character */

    puts(strA);   /* show string A */

    pA = strA;    /* point pA at string A */

    puts(pA);     /* show what pA is pointing to */

    pB = strB;    /* point pB at string B */

    putchar('\n');       /* move down one line on the screen */

    while(*pA != '\0')   /* line A (see text) */

    {

        *pB++ = *pA++;   /* line B (see text) */

    }

    *pB = '\0';          /* line C (see text) */

    puts(strB);          /* show strB on screen */

    return 0;

}

--------- end program 3.1 -------------------------------------


위의 코드에서 80개의 칸을 가진 두개의 배열이 정의 되면서 시작합니다., 전역적(globally)으로 정의되었기 때문에, 그들은 처음에 모두 '\0'로 초기화됩니다. 그리고 strA는 42개의 문자로 이루어진 문자열로 초기화 됩니다.


이제 main코드로 이동해서, 우리는 두개의 char 포인터를 선언합니다. 그리고 화면에 문자열을 띄웁니다. 그리고 포인터 pAstrA를 "가리킵니다". 그것이 변수 pAstrA[0]의 주소를 복사하는 할당문입니다. 이제 우리는 puts()를 사용하여 pA가 가리키는 내용을 화면에 띄웁니다. puts() 함수의 프로토타입을 살펴보겠습니다.


int puts(const char *s);


당분간은 const를 무시합니다.(번역자 : 추후 언급하겠다는 뜻인것 같습니다.) puts()의 매개 변수(번역자 : 인자라고들 하죠)는 포인터의 값입니다.(C에서는 모든 매개 변수는 값으로 전달되기 때문 (번역자 : call by value)) 그 포인터의 값은 가리키고 있는 주소입니다. 따라서 우리가 puts(strA);라고 사용할 때 우리는 strA[0]의 주소를 전달합니다.


비슷하게 우리가 puts(pA); 우리는 같은 주소를 전달합니다. 우리가 pA = strA라고 적었기 때문이죠.


코드를 따라 내려오는 line A의 while() 문을 감안합니다. Line A의 상태:


pA에 의해 가리켜지는 문자(즉 *pA)는 nul 문자가 아닌 동안(즉 마지막의 '\0')  다음을 수행:


Line B 상태: pA가 가리키는 문자를 pB가 가리키는 공간에 복사합니다. pA가 다음 글자로 증가하고 pB도 다음 공간으로 넘어갑니다.


마지막 문자를 복사했을 때, 이제 pA는 마지막 nul 문자를 가리킵니다. 그리고 loop를 끝냅니다. 그러나 우리는 nul 문자를 복사하지 않았습니다. 그리고 C의 문자열 정의에 의해 반드시 nul로 끝을 맺어야합니다. 그래서 우리는 nul 문자를 Line C에서 추가합니다.


strA, strB, pA 그리고 pB를 관찰하면서 디버깅하고 한단계씩 이 프로그램을 돌려보는 것은 매우 교육적입니다. 만약 위의 strB[]를 간단히 정의 보면 더 교육적입니다. 이렇게 초기화 해봅시다:


strB[80] = "12345678901234567890123456789012345678901234567890"


strA의 길이보다 훨씬 더 위의 변수를 관찰하면서 한 단계씩 반복합니다. 시도해보세요!


puts() 프로토타입으로 돌아가서, 매개 변수를 한정하기위해 사용된 "const"는 함수는 s가 가리키는 문자열이 수정되지 않을 것을 알려줍니다. 즉 이것은 문자열을 상수로 처리함을 말합니다.


물론 위의 프로그램이 설명하는 것은 string을 복사하는 간단한 방법입니다. 어떤 일이 일어나는지 잘 이해할때까지 위 프로그램을 돌려보고나서 우리는 C에서 제공하는 표준 함수 strcpy()를 대체하는 것을 만들어 낼 수 있습니다. 다음과 같이 말이죠:


char *my_strcpy(char *destination, char *source)

{

    char *p = destination;

    while (*source != '\0')

    {

        *p++ = *source++;

    }

    *p = '\0';

    return destination;

}


이 경우, 나는 목적지에 포인터를 return하는 보통순서를 따랐습니다.


다시 함수는 두 char 포인터 값을 받아들이도록 디자인 되었습니다. 즉, 주소죠. 따라서 이전 프로그램을 우리는 사용할 수 있습니다:


int main(void)

{

my_strcpy(strB, strA);

puts(strB);

}


나는 표준 C에서 사용하는 프로토타입에서 약간 벗어났다.


char *my_strcpy(char *destination, const char *source);


여기의 "const" 한정자는 soruce 포인터가 가리키고 있는 내용이 변하지 않을 것임을 보장하는 걸로 사용됐습니다. 당신은 "const" 한정자가 포함된 위의 기능을, 프로토 타입처럼, 수정함으로써 증명할 수 있습니다. 그리고나서 당신은 source가 가리키는 내용을 변화하는 시도를 추가해봅시다. 다음처럼요 :


*souce = 'X';


이는 일반적으로 string의 첫번째 문자를 X로 바꿉니다. const 한정자는 컴파일러가 발견하는 에러의 원인이 됩니다.(번역자 : 저렇게 바꾸면 에러납니다) 직접 해보시길 바랍니다.


이제, 위의 예시로 보여진 내용 몇몇을 생각해봅시다. 우선, *ptr++은 해석됩니다. ptr에 의해 가리켜지는 값을 return하고 포인터의 값을 증가합니다. 이것은 연산자의 우선순위와 관련 있습니다. 우리가 (*ptr)++이라 쓸 때 포인터가 아니라 포인터가 가리킄 값이 증가할 것입니다! 즉 만약 당신이 첫번째 글자를 'T'라고 적었다고 가정하면 그것은 'U'로 증가할 것입니다. 여러분들은 간단한 예시 코드를 통해서 이를 설명할 수 있습니다.


string은 마지막에 '\0'이 오는 문자의 배열 그 이상이 아니란 것을 다시 한번 기억합시다. 지금까지는 문자의 배열에서 동작하는 것이지만 이것은 integerm double 등등에서도 동일하게 적용됩니다. 그러나 이러한 경우에는 string의 처리와는 다르게 배열의 끝이 nul 문자 값으로 채워지지 않습니다. 우리는 끝을 식별하기 위해서 특별한 값을 참조하는 버젼을 구현할 수 있습니다. 예를 들어, 음수를 끝에 넣어서 양수의 배열을 복사할 수 있습니다. 반면에 string이외에 항목의 수를 복사하는 기능 뿐만 아니라 배열의 주소를 전달하는 것 등 우리가 어떤 항목(item)을 복사하는 것이 더 일반적입니다. 예를 들면 다음과 같은 프로토타입같은 일이 일어날 수도 있습니다:


void int_copy(int *ptrA, int *ptrB, int nbr);


nbr은 복사된 integer의 개수입니다. 당신은 이러한 integer 배열을 만들고 int_copy() 함수를 작성하여 동직시켜볼 수 있습니다.


이것은 큰 배열을 다루는 함수로도 허용됩니다. 예를들어 우리가 integer 5000개의 배열을 가지고 있다고 해봅시다. 그리고 우린 이것을 함수로 다루길 원합니다. 우리는 배열의 주소를 전달할 함수가 필요합니다(그리고 우리가 무엇을 무엇을 하고있는지 따라 위의 nbr같은 보조 정보). 배열은 그 자체가 전달하지 않습니다 즉, 전체 배열은 함수가 호출되기 전에는 stack에 복사되지 않습니다. 오직 주소가 보내집니다.


integer에서 함수에 전달되는 것과는 다릅니다. 우리가 integer를 전달할때 우리는 integer를 복사합니다. 즉, 이 값을 stack에 넣습니다. 함수안에서 전달된 값의 어떤 조작은 원래의 값(integer)에 영향을 줄 수 없습니다(no way). 그러나 배열과 포인터에서 변수의 주소를 전달 할 수 있고 그러므로 원래 변수의 값을 조작할 수 있습니다.

(번역자 : 즉 integer는 스택을 활용하여 인자를 전달하기에 원래 값과는 무관하지만 string은 주소값을 전달하여 원래의 값이 바뀔 수 있다 입니다.)



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

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