C에서 가변 배열(Variable-Length Array)

By | 2008/03/22

한 달 전 겨울 방학 때

학부에서 컴퓨터공학과 2학년 진입생들을 위해 C언어 강좌를 열었습니다.

그 시간표를 보니 1학년 때 배운 것 전부를 한다고 나와있더군요.

즉, 왠만한 C 언어 문법 책 전부를 일주일 만에 다 하겠다고 나와있었습니다.

물론 하루에 이론 3시간, 실습 3시간이라 시간은 많은지 모르겠지만,

과연 일주일만에 C 완성이라는 것이 가능한지 의문이었습니다.

하지만 저는 신청하였습니다.

왜냐하면 3년 동안 제대로 된 수업을 들지 않아서

수업을 듣는 감각을 잃어버리지 않았나 하는 걱정 때문이었습니다.

 

예상대로 수업을 통해 얻은 내용은 거의 없었습니다.

교수님 세 분이서 나눠 들어오시니 들은 내용 또 듣는 일도 있고,

각 교수님마다 걱정이 많으셔서 이런저런 잔소리가 많으셨습니다.OTL….

그래서 C를 배웠는지 진로 상담을 받았는지 모르겠더군요.;;;

 

하지만 중간중간에 제가 잘 몰랐던 부분이 있어 스프링노트에 필기하였습니다.

C언어 특강

그렇지만 자리 앞에 컴퓨터가 있어서 관련 내용을 구글에 검색하면서

더 많은 자료를 찾았습니다.

 

 

그러다 한 곳에서 재미있는 글을 발견하였습니다.

C/C++ (C99)의 가변 크기 배열

C99에서는 variable-length array가 지원이 된다는 내용입니다.

 

malloc로 당연히 되지 않느냐 라고 하실지 모르시겠지만,

우리가 흔히 배열을 선언할 때 배열의 크기를 지정할 때 정수 상수를 써야합니다.

예를 들어 int Arr[10]; 처럼 int 변수를 10개 가지는 배열을 만들겠다고 얘기해야죠.

(물론 크기를 지정하지 않아도 되지만, 제약이 많죠.)

 

하지만 그 외에 int A[i]; 라는 코드가 가능하다는 것입니다.

object님의 글에서 gcc에서 확인을 하셨다고하여 직접 해보았습니다.

c3

c2

위에 소스와 밑에 결과물입니다.

(소스는 밑에 따로 적겠습니다.)

(원래 작업하던 동아리 서버가 맛이 갔습니다.OTL….

그래서 잘 안 쓰는 학부서버로 한지라 느낌이 이상하네요.;;;)

 

함수 func는 문자열과 길이를 받아

인자로 받은 문자열 처음에서 길이만큼을 잘라내어 출력하도록 합니다.

여기서 함수 안에서 문자열을 담을 배열 크기를 인자로 온 변수인 length로 잡았습니다.

그럼에도 문제없이 컴파일, 링킹, 실행이 되는군요.^^

gcc 버젼을 살펴보니 3.4.6입니다.

 

그럼 이제 Visual C++ 6.0에서 확인해보겠습니다.

소스는 같은 소스입니다.

c5

역시 해당 부분에서 에러가 떴습니다.

constant expression가 나와야 한다고 하는군요.

 

 

윈도우에서 쓰는 gcc라고 불리는 Dev-C++에서 확인해보았습니다.

c8

문제 없이 컴파일이 됩니다.

c9

실행 역시 문제없이 되는군요.

 

어떻게 이런 결과가 나오는지 어셈블리 코드를 살펴봤습니다.

c6

죄송합니다. 모르겠습니다.OTL…

(이 머신의 OS가 Sun OS라서 st라는 명령어는 처음 보는 듯….OTL…..)

(후에 어셈블리를 좀 더 배우고나서 해석해보겠습니다.)

 

여하튼 gcc에서는 잘 되는군요.^^

(다른 컴파일러는 설치하기가 귀찮아서…

개인적으로 Intel에서 만든 컴파일러가 가장 기대됩니다.^^;;)

 

 

혹시나해서 제가 공부한 책인 ‘C 언어 펀더멘탈’을 찾아보았습니다.

거기에 이런 글이 적혀있습니다.

 

또한 요소의 개수를 명시해주는 숫자는 정수 상수여야 한다.

정확하게는 (널포인터 상수를 얘기할 때 언급한 적이 있는)정수 상수 수식이어야 한다.

이는 C에서 언어에 의해 제공되는 정적 혹은 자동 기억수명을 갖는 모든 대상체가

고정된 크기를 가져야 한다는 사실을 되새겨보면 쉽게 유추할 수 있다.

따라서 혹시라도 배열의 크기를 다음과 같은 방법으로

프로그램 실행 중에 변화시킬 수 있다고 생각해서는 곤란하다.

void func(int n)

{

    int array[n]; /* n개의 요소로 구성된 배열 선언*/

    ……

}

– 전웅, <C 언어 펀더멘탈>, 한빛미디어, 2005, pp. 400

 

곤란하다고 하는 방식이 제대로 되는군요.^^;;

이것이 진정 곤란한 듯…;;;;

 

해당 책은 장마다 마지막 부분에 ‘C99 이야기’라는 코너가 있습니다.

혹시나 해서 살펴보니 이런 내용이 적혀있습니다.

 

4. <p. 484> VLA 지원

C99의 경우에 다소 제한적이기는 하지만,

프로그램 실행 중에 크기가 변할 수 있는 배열인

VLA(Variable Length Array)를 지원한다.

– 전웅, <C 언어 펀더멘탈>, 한빛미디어, 2005, pp. 506

 

저는 처음에 이 부분을 읽을 때

‘484쪽에서 malloc를 얘기하였는데, VLA를 지원한다는 것은 무슨 뜻일까?’라며

별 대수롭지 않게 생각하였습니다.

하지만 ‘좀 더 자세히 얘기하였으면 좋았을 것을…’하는 아쉬움이 있습니다.ㅜㅜ

 

 

저도 뒷북이었다면 정말정말 죄송합니다.ㅜㅜ

 

 

PS

이 글을 처음에 해당 내용을 알고나서 바로 적으려고 했습니다.

그러다 다치바나 다카시의 ‘뇌를 단련하다’라는 책에서 재미있는 문구가 생각났기에

해당 문구를 첨부하여 글을 적고 싶었습니다.

하지만 이미 책을 도서관에 반납한터라 책을 읽고자 도서관으로 향하였습니다.

 

그런데 도서관에 있는 총 3권의 책 모두 대출이 된 것입니다.

제가 빌릴 때는 모두 남아 어느 것을 가져갈까 고민했습니다만,

며칠 사이에 인기가 많은 책이 될 줄 몰랐습니다.OTL…

그래서 후에 책을 보게 되면 글을 적기로 하였습니다.

 

며칠 전 도서관에서 책을 빌리다가 문득 그 책이 생각나서 검색해보니

마침 한 권이 서고에 있다는 것을 알게되었습니다.

그래서 바로 책을 빌린 다음에 해당 부분을 워드로 옮겨 적은 후 다시 반납하였습니다.

 

제가 찾고 싶었던 글은 바로 이것입니다.

 

내가 대학에 들어간 때가 1959년인데,

중학 시절 이래로 친하게 지내던 친구 중에 도쿄공대 수학과에 들어간 친구가 있는데,

그 친구하고는 이전부터 과학과 철학이 두루 관련된 문제를 자주 토론했습니다.

(중략)

대학에 입학한 지 얼마 지나지 않았을 때 그 친구가 나에게,

“이 세계의 가장 기본적인 법칙이 무엇인지 알고 있니?

이 세계가 어떻게 이루어져 있는지를 단 하나의 최종적인 법칙으로 정리한다면

어떤 법칙이 되는지 알고 있냐고?” 하고 물었습니다.

그런 걸 생각해 본 적도 없는 나는 잠시 이리저리 머리를 굴리다가

도저히 생각이 떠오르지 않아서, “모르겠는데” 하고 대답하자

“대칭성의 법칙이야. 이 세계는 기본적으로 대칭으로 만들어져 있다구.

다른 여러 가지 법칙들은 다 대칭성의 법칙에서 나온 거야.” 라고

득의양양하게 가르쳐 주었던 것입니다.

(중략)

그가 권하는 책을 사다 읽어 보니 과연 그런 내용이 나오더군요.

정확한 책 이름은 기억이 나지 않지만

서구 학자가 쓴 물리학 입문서의 번역서였던 것 같습니다.

지금 생각하면 양과 리의 패리티 비보존설과 실험적 증명이 1956년에 이루어졌으므로

그때면 벌써 세계에 널리 알려져 있었을 법한데,

그 책에는 그것이 언급되어 있지 않았습니다.

그러나 보통 한 권의 책을 쓰는 데 몇 년이 걸리고,

그것이 일본에서 번역되어 출판되는 데 또 몇 년이 걸리는 게 보통입니다.

(중략)

그래서 그때 읽은 책에 양과 리의 패리티 비보존 발견에 관한 언급이

전혀 없었던 것도 이해할 만합니다.

– 다치바나 다카시, 이규원 역, <뇌를 단련하다>, 청어람미디어, 2004, pp. 328, 329

 

가끔 친구나 후배에게 C를 얘기하면서

‘배열을 처음 정할 때 크기는 고정되어있어!’라고 얘기한 제가 부끄러워지더군요.

1956년에 나온 것을 1959년에 모르는 것은

그 때는 정보를 얻는 것이 느리기 때문일 것입니다.

하지만 지금은 인터넷 시대에서는 정보를 쉽고 빠르게 얻을 수 있으니

1999년에 나온 것을 2008년에 알게 된 것이 너무 부끄럽더군요.OTL….

 

 

참조

http://minjang.egloos.com/1727588

전웅, <C 언어 펀더멘탈>, 한빛미디어, 2005, pp. 400, 506

다치바나 다카시, 이규원 역, <뇌를 단련하다>, 청어람미디어, 2004, pp. 328, 329

http://en.wikipedia.org/wiki/C99#C99

 

 

#include <stdio.h>

void func(char* str, int length);

int main(void)
{
    char str[] = “I’m NoSyu. Hi~ World!”;
    int i = 0;
    for(i = 1 ; i < sizeof(str)-1 ; i++)
    {
        func(str, i);
    }
    for(i = sizeof(str)-1 ; i > 0 ; i–)
    {
        func(str, i);
    }
    return 0;
}

void func(char* str, int length)
{
    char cpArr[length];
    int i = 0;
    for(i = 0 ; i < length ; i++)
    {
        cpArr[i] = str[i];
    }
    cpArr[i] = ‘\0’;
    printf(“%s\n”, cpArr);
}

 

    .file    “variable_array.c”
    .section    “.rodata”
    .align 8
.LLC0:
    .asciz    “I’m NoSyu. Hi~ World!”
    .section    “.text”
    .align 4
    .global main
    .type    main, #function
    .proc    04
main:
    !#PROLOGUE# 0
    save    %sp, -144, %sp
    !#PROLOGUE# 1
    sethi    %hi(.LLC0), %g1
    or    %g1, %lo(.LLC0), %o3
    ld    [%o3], %o4
    ld    [%o3+4], %o5
    std    %o4, [%fp-40]
    ld    [%o3+8], %o4
    ld    [%o3+12], %o5
    std    %o4, [%fp-32]
    ld    [%o3+16], %g1
    st    %g1, [%fp-24]
    lduh    [%o3+20], %g1
    sth    %g1, [%fp-20]
    st    %g0, [%fp-44]
    mov    1, %g1
    st    %g1, [%fp-44]
.LL2:
    ld    [%fp-44], %g1
    cmp    %g1, 20
    bgu    .LL3
    nop
    add    %fp, -40, %g1
    mov    %g1, %o0
    ld    [%fp-44], %o1
    call    func, 0
     nop
    ld    [%fp-44], %g1
    add    %g1, 1, %g1
    st    %g1, [%fp-44]
    b    .LL2
     nop
.LL3:
    mov    21, %g1
    st    %g1, [%fp-44]
.LL5:
    ld    [%fp-44], %g1
    cmp    %g1, 0
    ble    .LL6
    nop
    add    %fp, -40, %g1
    mov    %g1, %o0
    ld    [%fp-44], %o1
    call    func, 0
     nop
    ld    [%fp-44], %g1
    add    %g1, -1, %g1
    st    %g1, [%fp-44]
    b    .LL5
     nop
.LL6:
    mov    0, %g1
    mov    %g1, %i0
    ret
    restore
    .size    main, .-main
    .section    “.rodata”
    .align 8
.LLC1:
    .asciz    “%s\n”
    .section    “.text”
    .align 4
    .global func
    .type    func, #function
    .proc    020
func:
    !#PROLOGUE# 0
    save    %sp, -128, %sp
    !#PROLOGUE# 1
    st    %i0, [%fp+68]
    st    %i1, [%fp+72]
    st    %sp, [%fp-24]
    ld    [%fp+72], %g1
    add    %g1, 7, %g1
    srl    %g1, 3, %g1
    sll    %g1, 3, %g1
    sub    %sp, %g1, %sp
    add    %sp, 92, %g1
    add    %g1, 7, %g1
    srl    %g1, 3, %g1
    sll    %g1, 3, %g1
    st    %g1, [%fp-28]
    st    %g0, [%fp-20]
    st    %g0, [%fp-20]
.LL9:
    ld    [%fp-20], %o5
    ld    [%fp+72], %g1
    cmp    %o5, %g1
    bge    .LL10
    nop
    ld    [%fp-20], %g1
    ld    [%fp-28], %o5
    add    %o5, %g1, %o4
    ld    [%fp+68], %o5
    ld    [%fp-20], %g1
    add    %o5, %g1, %g1
    ldub    [%g1], %g1
    stb    %g1, [%o4]
    ld    [%fp-20], %g1
    add    %g1, 1, %g1
    st    %g1, [%fp-20]
    b    .LL9
     nop
.LL10:
    ld    [%fp-20], %g1
    ld    [%fp-28], %o5
    add    %o5, %g1, %g1
    stb    %g0, [%g1]
    sethi    %hi(.LLC1), %g1
    or    %g1, %lo(.LLC1), %o0
    ld    [%fp-28], %o1
    call    printf, 0
     nop
    ld    [%fp-24], %sp
    ret
    restore
    .size    func, .-func
    .ident    “GCC: (GNU) 3.4.6”

8 thoughts on “C에서 가변 배열(Variable-Length Array)

  1. object

    SunOS라면 알파계열 칩이고 RISC 형식이라 대충 읽으니 되네요. st는 Store겠죠. 대충 암호 풀듯이 풀어보니 첫번째 인자가 $i0, 두번째 함수 인자가 $i1에 저장이 되네요.

    func:
    !#PROLOGUE# 0
    save %sp, -128, %sp
    !#PROLOGUE# 1
    st %i0, [%fp+68]
    st %i1, [%fp+72]
    st %sp, [%fp-24]

    이거는 그냥 stack 조정하고 인자들과 스택포인터를 스택에 저장하는 코드이구요.

    ld [%fp+72], %g1
    add %g1, 7, %g1
    srl %g1, 3, %g1
    sll %g1, 3, %g1

    이제 %g1 레지스터에 가변인자인 length 값을 읽어오고, 7을 더하고 right shift로 3칸, 다시 left shift로 3칸을 하는데 이거 보니까 alighment하는 거네요. length가 11이라고 하면, 7을 더하면 18, srl 3 (shift right logical)을 하면 2가 되고, 다시 left 3하면 16이 되겠죠. 즉, 11을 가장 가까운 8의 배수에다 정렬 시키는 겁니다. 16만큼 가변배열을 잡는거네요.

    sub %sp, %g1, %sp
    add %sp, 92, %g1
    add %g1, 7, %g1
    srl %g1, 3, %g1
    sll %g1, 3, %g1
    st %g1, [%fp-28]
    st %g0, [%fp-20]
    st %g0, [%fp-20]

    그런다음에 스택을 이 g1에 저장된 배열크기만큼 조정을 하네요. 그 뒤는 잘 모르겠습니다 ㅎㅎ

    어찌되었건… 예상할 수 있듯이 스택에다 잡는 것이기 때문에 스택포인터를 조정하는 코드가 있군요. 훌륭한 글입니다!

    Reply
  2. L_Psyfer

    동적할당의 다른 이름인가 하고 들어왔다가 좋은거 배우고 돌아갑니다 ^^

    Reply
  3. NoSyu

    /object/
    네.. 전에 확인해보니 Big Endian을 쓰고 있더군요.
    맞는 듯싶습니다.^^
    지금 코드를 다시보니 그렇네요.
    왜 저렇게 8의 배수에 맞추는지 모르겠지만, 그건 시스템 프로그래밍 하는 사람에게 물어봐야겠죠.^^;;;
    해석 달아주셔서 고맙습니다.
    저도 곧 어셈블리를 배워 이해해보겠습니다.^^ㅜㅜ

    Reply
  4. NoSyu

    /L_Psyfer/
    반갑습니다.
    저도 object님의 글을 통해 알 수 있었습니다.^^
    좋은 것을 얻으셨다니 다행이네요.^^

    Reply
  5. object

    이건 Endianess와는 전혀 상관이 없는 문제이구요. 8의 배수로 맞추는 건 그냥 메모리 읽기를 최적화 하기 위해서죠. Unaligned data는 읽는데 두 번의 로드가 필요할 수 있거든요. 그래서 메모리 주소들이 8바이트 정도 (아키텍처 마다 달라요) 배수가 되도록 정렬이 되는 겁니다.

    Reply
  6. NoSyu

    /object/
    네.. 맞습니다.^^
    다만, RISC가 맞는지 알 수 있는 방법이 없어
    Big Endian을 쓰는 것과 Sun OS인 점을 미뤄보아 RISC를 쓰지 않을까 추측했습니다.;;;;

    그러고보니 구조체를 만들 때도 정렬을 신경써야한다고 하던데,
    여기서는 컴파일러가 해주는군요.^^;;
    좋은 덧글 고맙습니다.

    Reply
  7. object

    그런 뜻이었군요 ㅎㅎ RISC인 것을 제가 안 것은 endianess 때문은 아니구요. 엔디안은 RISC/CISC 뭐 그런 것과는 전혀 상관이 없는 녀석입니다. 그리고 주어진 어셈에서 엔디안니스를 추측할 수도 없어보이구요. 암튼… RISC로 확인할 수 있는 건 간단한데요. 명령어가 간단하면 그냥 RISC 인 겁니다 ㅎㅎ

    구조체는 보통 컴파일러가 디폴트 정렬을 해줍니다.

    Reply
  8. NoSyu

    /object/
    네.. 전혀 상관없지만, 제가 매일 접하는 것이 32bit CISC Little Endian이다보니
    저 세 개가 동시에 따라다니는 생각을 하고 만 것입니다.OTL…
    (아직 저에게는 64bit가 없어요.ㅜㅜ)
    명령어가 간단하면 RISC이군요.^^

    구조체의 경우도 컴파일러가 디폴트 정렬을 해주는군요.
    왠만하면 직접 맞춰라, 비트 필드를 쓸 때 잘해라 등의 얘기를 들은터라
    컴파일러가 아직 멀었는가 하는 생각을 했습니다.
    저보다 똑똑한 사람들이 만든 컴파일러를 믿어야겠군요.^^;;;

    Reply

Leave a Reply