본문 바로가기
프로그래밍 언어/C 언어

[C] 문자열 배열과 문자열 배열 동적 할당

by 까망 하르방 2021. 3. 20.
반응형

문자열 배열과 문자열 배열 동적 할당

문자열의 배열을 2차원배열을 쓸 때의 문제점

C언어는 Java나 C++처럼 문자열을 다루는 string 자료형이 없다. 
대신에  문자열은 널 문자로 끝나는 문자(char) 타입의 배열을 사용한다.
 
즉, 일련의 문자열 집합을 다루려면 문자열의 배열을 사용한다.
여러 개의 문자열을 저장하기 위해서는
2차원 문자 배열을 사용한다.
char animal[5][20] = {"cat", "horse", "dog", "tiger", "eiephant"};
 
위의 경우, 크기가 일정하게 고정하여 생성해야 하므로,
각 문자열의 길이가 제각각 다르다면 메모리 낭비될 수 있습니다.
그렇기에 초기 C언어에서는 2차원 배열을 사용하는 방법이 추천되지는 않았다.
 
 

문자열 포인터의 배열

문자열은 char 타입의 배열로 나타낼 수 있고,
이는 char 타입 포인터로 대체하여 표현 가능하다. 
 
따라서 문자열의 문자열은 char*타입의 배열이나
char 타입의 2중 포인터인 char** 타입을 써서
메모리 낭비 문제를 해결할 수 있다.
#include <stdio.h>

int main(){
    char *arrs[] = {"apple", "banana", "cherry", "pomegrante", "pepper", "onion" };
    int i;
    char* p = arrs[0];
    for(i=0; i < 40; i++){
        if (*p != '\0') {
            printf("%c", *p);
        } else {
            printf("\n");
        }
        p++;
    }
    return 0;
}
 
문자열의 최대 길이로
각 요소의 길이가 고정되는 2차원 배열과는 달리, 
문자열의 시작위치에 대한 포인터들의 배열이며,
따라서 사용되지 않고 남는 메모리가 없다.
 
[다른 예시 코드]
#include <stdio.h>

int main(){
    char *ptr_ary[5];
    int i;
    ptr_ary[0] = "dog";
    ptr_ary[1] = "elephant";
    ptr_ary[2] = "horse";
    ptr_ary[3] = "tiger";
    ptr_ary[4] = "lion";

    for(i=0; i<5; i++)
        printf("%s\n", ptr_ary[i]);
    
    return 0;
}
 
 
 

 

 

2차원 배열처럼 활용하는 포인터 배열

int ary1[4] = {1, 2, 3, 4};

int ary2[4] = {11, 12, 13, 14};

int ary3[4] = {21, 22, 23, 24};

int *ptr_ary[3] = {ary1, ary2, ary3};

 

① ptr_ary + 2

ptr_ary + 2 = ptr_ary + (2 * sizeof(ptr_ary[0])) = 500 + (2 * 4) = 508

포인터배열의 세 번째 배열 요소를 가리키는 포인터가 구해집니다.

 

② *(ptr_ary + 2)

포인터 배열의 세 번째 배열요소의 값 300번지가 구해집니다.

 

③ *(ptr_ary + 2) + 2

300 + 2 → 300 + (2 * sizeof(ary3[0])) → 308

ary3 배열의 세 번째 기억공간을 가리키는 포인터가 구해집니다.

④ *(*(ptr_ary + 2) + 2)

308번지는 포인터 참조연산을 수행하면 값 23이 참조된다.

 

 

동적으로 할당되는 배열 사용하기

char[][] 타입으로 지정하는 것은
미리 고정 크기의 메모리를 할당해야 하기에
메모리 낭비와 같이 비효율적으로 자원을 관리합니다.
 
그렇다고 연속적인 공간에 문자열을 계속해서 저장하는 경우는
추가적인 레코드 삽입으로 메모리가 더 필요한 경우에
재할당을 요청하는 것은 성능의 영향을 줄 수 있습니다.
#include <stdio.h>
#include <stdlib.h> 
#include <string.h> 

int main(){
    int arraySize = 4, n = 0, l = 0; 
    char * names[arraySize];
    char buffer[30]; 

    while ( n < arraySize ) {
        scanf("%s", buffer);
        // 버퍼에 문자열을 입력받은 후, 그 길이 만큼 새 문자열을 위한 메모리를 할당한다.
        // 새로 할당된 메모리에 버퍼의 내용을 복사하고, 그 시작 위치를 배열에 추가한다.
        l = strlen(buffer);
        if (l > 0){
            char* newstrptr = (char*)malloc(sizeof(char) * (l + 1));
            strcpy(newstrptr, buffer);
            names[n] =  newstrptr;
            n++;
        } else {
            break;
        }
    }

    // 배열의 각 원소를 순회하면서, 해당 값의 메모리 번지에서 시작되는
    // 문자열을 출력한다. 출력한 후에는 해당 메모리를 해제하여 파괴한다.
    for(n = 0; n < arraySize; n++){
        printf("%02d: %s\n", n, names[n]);
        free(names[n]);
    }
    return 0;
}

 

초기에 정적으로 할당하고 새로운 문자열을 저장할 필요가 있을 때, 

동적영역에서 해당 문자열 크기만큼의 메모리만 할당하여 내용을 저장한다. 
 
그리고 원래의 문자열 배열은
각각의 원소에 해당하는 문자열의 시작 번지를 저장하게 된다.
 
이와 같은 방식으로 배열을 다루면,
배열 자체는 문자열 포인터를 저장하는 연속적인 구간이 된다. 
 
그리고 각 포인터가 가리키는 곳은 별도로 할당된 메모리 영역이며, 
이 경우 문자열을 사용하는 것에 대해서는
실제 2차원 배열 혹은 연속적인 문자열 영역과는 아무런 차이가 없을 수 있다.
 
결과적으로 문자열의 길이가 얼마가 됐든지에도 무관하며, 
배열을 선언한 크기보다 작은 개수의 문자열만 쓰는 경우에도
메모리 낭비를 최소화할 수 있다.
 
반응형

'프로그래밍 언어 > C 언어' 카테고리의 다른 글

[C] 배열과 문자열 관계  (0) 2021.03.20
[C] 배열 선언 및 초기화  (0) 2021.03.20
[C] 함수의 선언  (0) 2021.03.19
[C] 문자열 관련 함수  (0) 2021.03.19
[C] 지역변수와 전역변수  (0) 2021.03.19

댓글