본문으로 바로가기
homeimage
  1. Home
  2. 컴퓨터/프로그래밍
  3. C언어 배열과 포인터 같다? 다르다?

C언어 배열과 포인터 같다? 다르다?

· 댓글개 · 바다야크

포인터 때문에 C언어가 어렵다는 분이 참 많습니다. 기계어나 어셈블리어부터 하신 분이라면 쉽게 이해하지만, 머릿속에 메모리를 그려 놓고 생각하는 것이 프로그래밍 입문자로써는 힘들 수 있습니다. 어차피 변수인데 이름에 따라 적당한 값을 넣고 뺀다고 생각하면 되는 것을 포인터라는 것은 이해했다고 싶어도 막상 프로그래밍을 하다 보면 헷갈립니다.

포인터를 정확히 이해하기 위해서는 배열과 어떤 차이가 있는지 알아야 합니다. C언어에서 배열과 포인터가 어떻게 다른지 자세히 알아보겠습니다.

C언어 배열과 포인터가 같을까?

C언어 코딩의 혼란스러움

더욱이 C언어의 친절함이라고 할까요? 배려 때문인지 모르지만, 배열과 함께 코딩하다 보면 더욱 헷갈립니다. 어? 코딩하는 방법이 배열이나 포인터나 똑같네. 그럼 왜 어렵게 포인터를 사용하나? 그냥 배열을 쓰지. 왜 이렇게 생각되냐면 포인터 변수와 배열 변수를 다루는 방법이 비슷하기 때문입니다. 아래의 코드들 보시죠.

#include <stdio.h>

int main( void)
{
    char ary[20] = "badayak.com";
    char *ptr;

    ptr = ary;
    *(ptr+0) = 'B';
    *(ary+1) = 'A';

    ptr[2] = 'D';
    ary[3] = 'A';

    printf( "%s\n", ptr);
    printf( "%s\n", ary);

    return 0;
}

배열 변수 ary와 포인터 변수 ptr을 다루는 방법이 똑같습니다. ptr = ary; 코드처럼 ptr에 ary 값을 대입했습니다. 같은 변수 타입이니까 가능한 코드라고 생각할 수 있습니다. *()와 []를 사용하는 것도 같은 방법으로 사용할 수 있고요, printf() 함수 내에서도 변수 이름만 넣었습니다.

메모리를 차지하는 포인터 변수. 그렇다면 배열 변수는?

이렇게 똑같은 모습으로 코딩할 수 있는 것을 보면 배열과 포인터는 같은 것일까요? 아닙니다. 가장 큰 차이는 변수 이름 자체에 대한 메모리 할당입니다. 포인터 변수 ptr은 메모리를 할당받습니다. 메모리가 있으니 당연히 메모리의 위치, 즉 주소를 갖습니다.

그렇다면 배열 변수 a는? 메모리가 있을까요? 없습니다. ptr = ary; printf( "%s\n", ary);처럼 배열 변수 이름만 입력해서 사용할 수 있어서 마치 포인터 변수처럼 a도 메모리를 차지하는 것처럼 오해할 수 있으나 없습니다. 배열 변수 char a[20]에서 메모리 할당을 받는 것은 a[0], a[1], a[2], .... a[19]만이 있으며, 당연히 메모리 주소도 a[0], a[1], a[2], .... a[19]만 갖습니다.

그렇다면 어떻게 ptr = ary; 같은 코딩이 가능할까요? 그것은 ary가 &ary[0]이기 때문입니다. 만일 C언어가 문법에 매우 엄격했다면 ptr = &ary[0]; 이라고 작성해야 인정했을 것입니다. 그런데 프로그래머에 대한 편의를 제공하기 위해서인지 C 컴파일러는 ary를 &ary[0]으로 해석해 줍니다. 이래서 C언어의 친절이니 배려라는 말을 썼습니다.

배열 메모리 구조
배열 메모리 구조

그림으로 다시 말씀드리면 char ary[20]; 이라고 선언하면 ary[0], ary[1], ... ary[19]는 메모리를 차지하며 각 요소는 메모리 주소 값을 갖습니다. 그럼 ary 이름에 대한 메모리는 없습니다.

포인터 메모리 구조
포인터 메모리 구조

포인터 변수를 메모리를 많이 차지하는 타입으로 생각되지만, 실제로는 메모리의 주소 값만 넣을 수 있는 작은 변수입니다. 32bit 시스템에서는 4바이트, 64bit 시스템에서는 8바이트짜리 정수 변수일뿐이죠.

C언어에서 배열만 사용하면 되지 않나?

배열이나 포인터나 어차피 특정 메모리 영역을 처리하는 변수라면 골치 아프게 포인터를 사용하지 말고 배열만 사용하면 되지 않나 생각할 수 있겠지만, 프로그래밍은 함수의 조합이라고 생각한다면 불가합니다.

#include <stdio.h>
#include <ctype.h>

void to_upper( char ary[20])   // <---- (1)
{
    int   ndx;

    for (ndx = 0; ndx < 20; ndx++){
        if ( isalpha(ary[ndx]))
            ary[ndx] &= 0B11011111;
    }
}

int main( void)
{
    char ary[20] = "badayak.com";

    to_upper( ary);
    printf( "%s\n", ary);

    return 0;
}

이렇게 배열로만 작성하면 되지 않을까요? 그러나 안타깝게도 프로그램 모습은 죄다 배열만 사용한 것처럼 보이지만, to_upper() 인수 char ary[20]은 배열이 아니라 포인터입니다. 프로그램을 수정해서 void to_upper( char ary[1000000]) 이라고 바꾸든 아예 ary[]라고 고쳐도 에러 없이 컴파일이 되고 실행됩니다. 왜냐하면 C언어에서 함수 인수 내에 표시되는 배열의 크기 지정은 아무런 의미가 없기 때문입니다.

#include <stdio.h>

void to_upper( char ary[20])
{
    printf( "to_upper()   %ld\n", sizeof( ary));        // <--- (1)
}

int main( void)
{
    char ary[20] = "badayak.com";

    printf( "main()   %ld\n", sizeof( ary));           // <--- (2)
    to_upper( ary);

    return 0;
}

main() 함수와 to_upper() 함수에서 똑 같이 char ary[20]으로 선언했습니다. 실행하면 둘 다 sizeof( ary) 값이 같을까요? 실행해 보시면 main()에서는 20이라고 출력되지만, to_upper()은 32bit 시스템에서는 4로, 64bit 시스템에서는 8로 출력될 것입니다. 즉, 함수의 인수가 배열로 선언해도 실제로는 포인터입니다. 그래서 함수 인수에서는 char ary[20], char ary[100000], char ary[], char *ary 가 모두 같은 얘기입니다.

C언어 입문자를 힘들게 하는 C언어

그렇다면 뭘 조심해야 할까요? 배열로 선언했다고 sizeof() 함수로 배열 크기를 따져서는 안 됩니다.

void to_upper( char ary[20])
{
    int   ndx;

    for (ndx = 0; ndx < sizeof( ary); ndx++){   // <-- sizeof()를 사용
        if ( isalpha(ary[ndx]))
            ary[ndx] &= 0B11011111;
    }
}

함수 인수에 배열 크기를 명시했다고 해도 sizeof( ary)의 값이 20이 아닙니다. 이 코드를 32bit 시스템에서 실행하면 앞에 4자까지만 대문자가 되고, 64bit에서는 8개 문자만 대문자로 바뀔 것입니다. C언어의 특성을 모르는 분이라면 환장할 것입니다. 분명히 ary[20]을 보냈는데 20개 전체는 안 바뀌고 앞에 몇 개만 바뀌니 말이죠.

#include <stdio.h>
#include <ctype.h>

void to_upper( char ary[], int sz_ary)
{
    int   ndx;

    for (ndx = 0; ndx < sz_ary; ndx++){
        if ( isalpha(ary[ndx]))
            ary[ndx] &= 0B11011111;
    }
}

int main( void)
{
    char ary[20] = "badayak.com";

    to_upper( ary, sizeof( ary)); // <--- (1)

    return 0;
}

이런 이유로 배열이든 포인터를 받는 함수는 실제 크기를 요구합니다.

점점 친절해지는 C언어

정말 미치겠네, 분명히 시리얼 포트로 1000 바이트를 보냈는데, 왜 4바이트만 받고 끝이지? 아예 못 받는 것도 아니고.

이런 일이 생기는 것은 C언어의 배열과 포인터에 대한 이해가 부족하기 때문입니다. C언어의 친절함 또는 배려인지, 아니면 자유분방함 때문인지 몰라도 포인터와 배열을 다루는 방법이 비슷해서 C언어 입문자를 매우 힘들게 합니다.

~/tmp$ gcc test.c
test.c: In function ‘to_upper’:
test.c:5:41: warning: ‘sizeof’ on array function parameter ‘ary’
             will return size of ‘char *’ [-Wsizeof-array-argument]
    5 |     printf( "to_upper()   %ld\n", sizeof( ary));
      |                                         ^
test.c:3:21: note: declared here
    3 | void to_upper( char ary[20])
      |                ~~~~~^~~~~~~
~/tmp$

아우~ 그래도 요즘 C 컴파일러는 경고로 알려 주네요. ary 변수에 대해서 포인터 크기로 반환한다고 말이죠. 변수 선언된 위치까지 매우 친절해졌습니다. gets()를 사용하면 fgets()로 바꾸라고 알려 줍니다.

이렇게 친절해 지기보다는 문법이 좀 엄격해졌으면 좋겠습니다.

  • ptr = ary; 는 에러. ptr = &ary[0]; 이 O.K.
  • to_upper( char ary[2]) 은 에러, to_upper( char *ptr)이 O.K.

이렇게 말이죠. 처음 학습할 때는 배열을 던졌는데 왜 배열로 받지 못하나 의아해 할 수 있지만, 왜 틀렸는지 따져보게 될 것입니다. 그러나 그 오랜 시간 동안 C언어가 바뀌지 않았다면 뭔가 깊은 뜻이 있겠지요.

이 외에도 포인터 이해를 어렵게 하는 C언어의 특성이 있습니다. C언어의 배열과 포인터에 대한 글을 올렸으니 참고하세요.

 

C언어 포인터와 배열 쉽게 이해하는 방법 1부

이 글은 C의 포인터를 학습 수준에서 알기는 아는데 정확히 모르는 분을 위한 글입니다. C언어를 학습하다 보면 대부분 포인터가 제일 어렵다고 합니다. 프로그래밍 경력자도 C언어는 자유도가

badayak.com

 

C언어 포인터 쉽게 이해하기 2부

C언어 포인터 이해하기 1부에 이어 2부 글입니다. 이전에는 배열과 비교하여 포인터에 대해 알아 보았는데요, 이번에는 C언어의 포인터에서 아리까리 헷깔리는 부분에 대해서 알아보겠습니다.

badayak.com

 

SNS 공유하기
💬 댓글 개
최근글
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.