본문 바로가기

awesome-c Beginner 번역/A tutorial on pointers

<비공식 번역>awesome-c Beginner 번역 : CHAPTER 6 : Some more on Strings, and Arrays of Strings

현재위치


<주의!!>

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




CHAPTER 6: Some more on Strings, and Arrays of Strings

(챕터 6: 문자열을 조금 더, 문자열의 배열)


음, 조금만 문자열로 돌아가봅시다. 다음의 모든 할당에서 main()을 포함한 어떠한 기능도 밖에서 만들진 것으로 이해합니다.


우리는 우리가 이렇게 쓸 수 있었던 이전의 chapter에서 지적했습니다 :


char my_string[40] = "Ted";


이 문장은 40byte의 공간을 배열로 할당할 것입니다. 그리고 맨 앞부터 4byte는(""내부의 문자 3개와 종료처리를 할 '\0') 문자열이 채워질 것입니다.


실제로, 만약 우리가 "Ted"라는 이름을 저장하고 싶다면 이렇게 쓸 수 있습니다. :


char my_name[] = "Ted";


그리고 컴파일러는 문자들을 셀 것입니다. nul 문자가 있는 공간에 도착하고 전체 네개의 문자를 배열이름에 의해 리턴되는 메모리 위치에 저장합니다. 이번 경우에는 my_name입니다.


몇몇 코드에서, 위에 방법 대신에 또 다른 입력 방법으로 이렇게 볼 수도 있습니다:


char *my_name = "Ted";


둘은 차이점이 있나요? 정답은... 있습니다. 배열 표기법을 사용하여 고정된 메모리 블록의 저장공간의 4byte를 각각의 문자들과 끝의 nul 문자로 채워집니다. 그러나 포인터 표기법에서는 동일한 4byte외에도 포인터 변수 my_name을 저장할 N byte가 더 요구됩니다(N의 크기는 시스템에 따라 바뀝니다. 그러나 보통은 최소 2byte에서 4byte 이상일 수 있습니다.)


배열 표기법에서 my_name은 배열의 첫번째 요소의 주소인 &my_name[0]의 축약입니다. 배열의 위치는 실행되는 동안 고정되어 있기 때문에, 이것은 상수입니다(변수가 아닙니다). 포인터 표기법에서는 my_name은 변수입니다. 더 좋은 방법은 여러분이 나머지 프로그램 내부에서 하는 것에 따라 다릅니다.


한단계 더 나아가서 만약 각각의 이 선언들이 모든 함수의 경계 밖인 전역적(globlally)의 반대인 함수 내부에서 수행하는 경우를 생각해봅시다.


void my_function_A(char *ptr)

{

char a[] = "ABCED"

.

.

}


void my_function_B(char *ptr)

{

char *cp = "FGHIJ"

.

.

}


my_function_A의 경우 배열 a[]의 내용 또는 변수(들)은 데이터로 간주합니다. 배열은 ABCDE 값으로 초기화 되었습니다. my_function_B의 경우 포인터 cp의 값도 데이터로 간주합니다. 포인터는 string FGHIJ를 가리키도록 초기화 되었습니다. my_function_A와 my_function_B에서 정의는 지역 변수입니다. 따라서 string ABCDE는 포인터 cp의 값과 같이 stack에 저장됩니다. string FGHIJ는 어디에라도 저장될 수 있습니다. 제 시스템에서는 data segment에 저장됩니다.


그런데, 제가 my_function_A에서 했던 배열의 자동 초기화는 기존 K&R C와 "새시대로 불리는" 새로운 ANSI C에선 불법입니다. 이 사실은 휴대성과 이전 버젼과의 호환성을 고려할 때 중요할 지도 모릅니다.


포인터와 배열의 관계/차이에 대한 토의가 길었습니다. 이제 다차원 배열로 가봅시다. 다음 예를 한번 생각해보죠 :


char multi[5][10];


이것이 무슨 의미일까요?, 음 다음 강조하는 부분을 잘 생각해보세요.


char multi[5][10];


배열의 이름 부분에 밑줄을 봅시다. 앞에 붙은 char와 뒤에 붙은 [10]은 10개의 문자 배열을 의미합니다. 그러나 multi[5]라는 이름은 그 자체만으로 5개의 요소를 가지는 배열이며 각 요소가 10개의 문자의 배열을 가지고 있다는 표시입니다. 따라서 10개의 문자를 저장하는 배열을 5개 가지고 있다는 뜻이지요.


우리가 어떠한 종류의 데이터로 2차원 배열을 채운다고 생각해봅시다. 메모리에서 이것은 5개의 분리된 배열을 초기화하는 형태로 보일 수 있습니다. 다음과 같이 말이죠 :


multi[0] = {'0','1','2','3','4','5','6','7','8','9'}

multi[1] = {'a','b','c','d','e','f','g','h','i','j'}

multi[2] = {'A','B','C','D','E','F','G','H','I','J'}

multi[3] = {'9','8','7','6','5','4','3','2','1','0'}

multi[4] = {'J','I','H','G','F','E','D','C','B','A'}


동시에, 개개의 요소들은 이러한 구문을 사용하여 주소화 할 수 있습니다 :


multi[0][3] = '3'

multi[1][7] = 'h'

multi[4][0] = 'J'


배열은 메모리내에서 연속적인 공간이기 때문에, 위의 예에서 실제 메모리 블록은 다음과 같은 것입니다:


0123456789abcdefghijABCDEFGHIJ9876543210JIHGFEDCBA

^

|_____ starting at the address &multi[0][0] (번역 : 배열의 시작하는 주소 &multi[0][0])


제가 multi[0] = "0123456789"라고 쓰지 않은 것을 명심하셔야합니다. '\0'으로 종료했던 것은 언제든지 ""(큰따옴표)내부에는 '\0' 문자를 표기하지 않아도 암묵적으로 추가되어 사용됨을 의미해왔습니다. 만약 그랬다면(이전챕터처럼 생각했다면) 저는 행당 10개 문자 대신 11개의 문자로 설정했을 것입니다.


위에서 나타내고자 했던 저의 목표는 2차원 배열이 어떻게 메모리에 위치하는지를 묘사하는 것입니다. char의 2차원 배열은 "string"의 배열과는 다릅니다.


이제, 얼마나 많은 열을 다루는지 알고있는 컴파일러는 multi+1을 2번째 행의 주소로 해석 할 수 있습니다. 그것에 열의 숫자인 10을 더한다면 그 위치를 가져올 수 있습니다. 만약 우리가 integer 그리고 동일한 차원의 배열을 다룰 경우 컴파일러는 10*sizeof(int)를 추가할 것입니다. 저의 컴퓨터에선 이것은 20입니다. 따라서 4번째 행의 9의 주소는 &multi[3][0] 또는 포인터 표기법으로 *(multi + 3)이 될 것입니다. 4번째 행의 2번째 내용을 얻기 위해서는 우리는 이 주소에 1을 더하고 그 결과를 역참조하면 됩니다.


*(*(multi + 3) + 1)


조금 생각해보면 우리는 이것을 볼 수 있습니다 :


*(*multi + row) + col)     and

multi[row][col]          yield the sam results.


다음 프로그램은 char 배열 대신에 integer 배열을 사용하여 설명하였습니다.

------------------- program 6.1 ----------------------


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


#include <stdio.h>

#define ROWS 5

#define COLS 10


int multi[ROWS][COLS];


int main(void)

{

    int row, col;

    for (row = 0; row < ROWS; row++)

    {

        for (col = 0; col < COLS; col++)

        {

            multi[row][col] = row*col;

        }

    }


    for (row = 0; row < ROWS; row++)

    {

        for (col = 0; col < COLS; col++)

        {

            printf("\n%d  ",multi[row][col]);

            printf("%d ",*(*(multi + row) + col));

        }

    }


    return 0;

}

----------------- end of program 6.1 ---------------------


포인터 버젼에선 2중 역참조가 요구되기 때문에, 2차원 배열의 이름은 종종 포인터의 포인터로 동일시 되기도 합니다. 3차원 배열에선 우리는 배열의 배열의 배열을 다루고 이것은 포인터의 포인터의 포인터가 되기도 합니다. 그러나 배열의 메모리 블록의 초기화는 배열 표기법의 정의를 사용합니다. 따라서 우리는 이것을 상수로서 사용하지, 변수로서 사용하지 않습니다. 변수 포인터가 아니라 고정된 주소로 이야기합니다. 위 프로그램의 역참조 함수는 주소의 값 변화가 요구되지 않는 배열의 요소에 접근을 허가하도록 사용했습니다(multi[0][0]의 주소는 multi의 symbol에 의한 것과 같습니다).

 

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

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