C에서 씨앗으로

통역이나 번역을 하는 분들이 대단하다는 것은 두 나라 언어와 문화에 모두 정통하다는 것이다. 그래서 어떤 프로그래밍 언어를 공부하려면, 기존에 자기가 알고 있던 언어로 작성된 소스 코드를 새로운 언어로 옮겨보는 것이다. 그렇게 하면 상당히 귗낳고 어려운 작업이 되기는 하지만, 두 언어에 대해서 상당한 경지에 이를 수 있다. 단지 언어의 겉모습, 즉 구문론(syntactic)적인 접근에서 벗어나 언어의 의미론(semantic)에 대해서도 무척 깊은 생각을 하게 되기 때문이다.
아래는 게시판에 어떤 분이 올리신 "C언어에서 씨앗으로 옮기는 것"에 대해서 내가 직접 쓴 답변이다. 이 답변을 작성하면서 막연하게 알고있었던
CC
C
언어의 의미론적인 특징을 보다 더 체계화 할 수 있는 큰 공부가 되었다.

 
차례
 
한 언어의 바탕글을 다른 언어로 바꾸는 것은 상당한 인내와 노력과 어느 정도의 지식이 필요한 작업입니다. 가령 C 바탕글을 씨앗으로 바꾸려면, 두 언어 모두에 대해 어느 정도의 지식이 필요합니다. 만약 두 언어가 지향하는 점을 잘 모르고 있거나 헷갈리게 된다면, 바탕글은 뒤범벅이 될 것입니다. 이는 제가 처음 씨앗을 배울 때 C의 바탕글을 고치면서 배웠기 때문에 누구보다도 많은 경험을 했던 것이지요.
질문자님 역시 C에 대해 상당히 해박한 지식을 갖고 있는 분 같은데, 아직까지 씨앗에 대해서는 많이 공부하지 않으신 것 같군요. 이번 기회를 빌어 씨앗의 깊은 곳을 이해하실 수 있었으면 좋겠습니다.
 

1 . 먼저 이중포인터...

질문
밑에 C문을 씨앗으로 옮기려면 어떻게...
 
int search_block(char *argument, char **list) { ~~~~~~어쩌구저쩌구~~~~~~ }
 
저는 대충
 
구역찾기(글자^ 명령글줄, 글자^^ 목록) => 정수 { ~~~~~~어쩌구저쩌구~~~~~~ }
 
일단은 이렇게 썼는데, 이게 맞는지, 책을 읽다가 헷갈려서 그러는데요... 이런 건 열린 배열로 받아야 한다느니 그런거같아서요. C 에서 * 는 씨앗에서 ^ 이거 아닌 가요? 쩝.... 씨에선 형식인자 받을 때 & 표시 안쓰던거같던데...흐흠.. 그리고 씨앗 책들(사용자설명서 + 기본단원설명서) 에선... '글자^^' 이런 표현 안썼던거같던데... 혹시 숨어있을지도 모르니깐 뭐라고 말은 못하겠네요.

 
답변
우선 위에서 제시하신 예는 C를 씨앗으로 직역했다고 생각할 때는 맞는 것입니다. 두 언어 간의 간접 연산자가 C는 *, 씨앗은 ^로 서로 다르고, 또한 *는 앞가지 연산자인데 반해 ^는 뒷가지 연산자라는 점이 다르지요. 그래서 대상물 앞에 *을 써준 것을 씨앗으로 고치면, 대상물 뒤에 ^를 쓰면 되겠지요.
그리고 사용자 설명서에는 "글자^^"와 같은 표현은 없지만, "모든 자료형에 대한 접근형을 둘 수 있다"는 가정 속에 이것이 포함되는 것입니다. '모든 자료형'이란 바로 '접근형 자신'도 포함된다는 소리이지요. 즉 접근형의 접근형도 만들 수 있다는 것을 묵시적으로 적어두는 것입니다. 씨앗 언어 정의에서 접근형 부분을 다시 한번 읽어보세요.
 

2. C와 씨앗에서의 '배열과 포인터의 관계'

질문
C 에선 배열이 포인터와 개념이 그러니깐, 주소에 대한 개념이 같다고 하자나요... 씨앗에선 아닌 가요? 왜 배열과 포인터를 섞어 쓰는데 안되죠? 책에서도 섞어 쓴 예가 없어서리... 예를들면....C에서...
 
char *command[] = { "north", "east", "south", "west", "\n" }
 
이걸 씨앗에선...
 
글자^[5] 명령어 := { "북", "동", "남", "서", "\줄" }
 
이거면 안되나요? 물론 안되더군요..흐~~~~ 이걸 막 이리바꿔보고 저리 바꿔보고 하다가.... 예를 들면
 
글자[5]^ 명령어 .............
 
아님
 
글자^[5] 명령어 := { 북, 동, 남, 서, \줄 }
 
이렇게도 해 보구.. 기타 등등...해 볼껀 왠만하면 다 해 봤음.
왜 자꾸 자료형이 틀리다고 나오죠? 1001 번 에러도 나보고 잘못된 자료주소 에러도 나고...흑흑 하다가 글자[5][3] 하니깐 에러 안나긴 하던데..... 제가 원하는건 이런거 말구.... 배열포인터....
 
답변
우선 답변을 드리기 전에 한 가지 말씀드릴 것이 있습니다. "씨앗은 C가 아닙니다." 제가 늘 말씀드리는 것이지만, 씨앗은 파스칼이 C와 다른 것 보다 더 C와 다릅니다. 왜냐하면 씨앗을 설계할 때 애초부터 C언어의 문법을 따라 만들지 않았기 때문입니다. { }, ++--, /* */ 등과 같은 일부 기호들의 쓰임이 같다고 해서, 씨앗의 문법마저 C와 같다고 생각하시면 큰 잘못입니다.
씨앗과 C의 문법상의 차이가 가장 잘 드러나는 부분이 바로 이 '배열과 접근형'에 있습니다. 씨앗은 씨앗입니다. C의 색안경을 쓰고 씨앗을 보지 마세요. 저 또한 처음에 질문자 님과 같이 씨앗을 씨앗으로 보지 않아서 많은 혼란에 빠진 적이 있지요.
우선, 혼란을 막기 위해 C언어와 씨앗에서의 배열과 접근형에 대한 개념을 정리해 두기로 합시다. 이 다음부터 늘 아래의 선언이 사용되므로 주의 깊게 읽어주세요.
 
char c, *cp, ca[80]; 글자 글. 글자^ 글접, 글자[80] 글배.
 
질문자 님은 "C 에선 배열이 포인터와 개념이, 그러니깐 주소에 대한 개념이 같다"라고 말씀하셨습니다. 대충 들으면 맞는 말씀 같지만, 사실은 그렇지 않습니다. 배열과 포인터는 엄연히 다른 대상체이고, 다른 일을 하며, 서로 다른 특성을 가지고 있습니다. 그러므로 둘의 개념은 전혀 같지 않고, 또 같을 수도 없습니다. C를 만든 사람이 좀 지저분한 생각을 가진 게 사실입니다. 세상에.. 포인터와 배열을 섞어놓다니... 그런 얼토당토 않는 프로그래밍 논리가 어디서 생겼는지... 여하튼 문제가 심각합니다.
C를 쓰는 사람들이 '배열과 포인터는 같다'라고 오해하는 이유는 대략 다음과 같습니다.
첫째, 접근하는 방법이 같습니다. 배열인 caca[3], *(ca+3)과 같이 접근할 수 있고, 접근형인 cp*(cp+3), cp[3]과 같이 접근할 수 있습니다. 즉 cp[3]은 내부적으로 *(cp+3)으로 변환이 됩니다. 즉 배열이든 접근형이든 그것이 가리키고 있는 대상에 접근하는 방법은 같습니다.
둘째, 함수에 실인자로 배열을 전달할 수 없다는 크나큰 단점 때문입니다. 실재로
 
int test( char table[3] ); int test( char table[] ); int test( char *table );
 
위의 세 선언은 모두 같은 의미를 가집니다. 선언할 때 배열 형식을 취할 수는 있지만, 결국 C에서는 배열을 함수에 전달할 수 없기 때문에 늘 포인터로만 전달이 되는 것이지요. 물론 배열과 접근형에 접근하는 방법이 같으니까 함수를 작성할 때 배열변수 쓰듯이 할 수 있지만, 엄연히 함수의 실인자는 배열이 아닌 접근형이라는 것을 알아야 합니다..
셋째, 배열 변수의 이름이 쓰일 때에는 그것의 주소값이 산출된다는데 문제가 됩니다. 가령
 
puts( ca );
 
처럼 했다면, ca는 바로 글자의 접근형(char *)이 되는 것이고, ca의 주소값이 puts()에 전달되게 됩니다. 즉 따로 주소 연산자(&)를 쓸 필요가 없고, 또 써서도 안됩니다. 솔직히 생각해 봅시다. 이런 엉터리가 어디 있습니까?
 
scanf( "%c", &c ); scnaf( "%s", ca ); scanf( "%s", cp );
 
scanf() 함수의 두 번째 인자부터는 접근형, 즉 주소값이 들어가야 한다는 건 아시겠죠? 그러므로 첫 번째와 두 번째의 경우는 말이 됩니다. 하지만 바탕글 상으로 볼 때 두 번째의 경우는 비합리적이지요. 어떻게 명시적으로 주소를 얻겠다고 하지도 않았는데, 배열 이름 그 자체가 주소가 될 수 있겠습니까? 그렇지 않아요? 바로 C를 설계한 사람이 '고급 적인' 생각을 하지 않고, 기계어로 번역하기 좋은 '저급 적인' 생각을 가지고 있었기 때문에 이런 비합리적인 일이 일어나는 것이지요.
만약 scanf()를 처음 배운 사람에게 위에 나열된 세 가지의 scanf()에 대해 설명하려면... 으~ 그 비합리성 때문에 처음부터 포인터를 모르고는 들어갈 수가 없지요.
덧붙여서.. 다음의 경우를 봅시다.
 
puts( ca ); // (1) sizeof( ca ); // (2)
 
위에서 (1)과 (2)의 문맥은 전혀 다릅니다. (1)에서와 같이 보통으로 배열 이름이 사용되면 접근형으로 간주하지만, sizeof 연산자에 배열 이름이 들어 갔을 때에는 그 배열 변수 자체를 이르게 됩니다. 뭐 '포인터 생성 규칙' 어쩌구 해서 규칙이 있기는 하지만, 이렇게 상황에 따라 바뀌어서야 논리적인 생각을 하기가 힘들겠지요? 또 그만큼 헷갈리고 실수할 확률도 많고요.
요컨대, 많은 사람들이 배열과 접근형의 관계를 혼동하는 이유는, 1) 접근 방식이 같기 때문이고, 2) 함수의 실인자로 배열을 전달할 수 없기 때문이고, 3) 배열의 이름 그 자체가 배열의 주소값―즉 접근형―이 된다는 것 때문이지요. 언뜻 편리하게 생각되는 기능들이지만 바탕글의 기독성과 논리성을 크게 헤치고 있는 것들입니다.
그런데 씨앗에서는 C언어에서 허용하고 있는 위의 세 가지 규칙들이 적용되지 않기 때문입습니다. 환언하면 씨앗에서는, 1) 배열과 접근형은 그 접근 방식이 완전히 다르고, 2) 함수의 실인자로 접근형은 물론 배열 변수까지도 전달할 수 있으며, 3) 배열의 이름 그 자체는 그냥 배열 전체를 의미하지, C처럼 접근형으로 변환취급되지 않는다는 것입니다.
하나 하나 예를 들어 설명하지요.
 

1) 배열과 접근형의 접근 방식이 다르다.

C에서 *(ca+3)과 같이 배열인데도 접근형처럼 다룰 수 있고, cp[3]과 같이 접근형인데도 배열처럼 다룰 수 있는데 반하여, 씨앗은 이를 절대로 허용하지 않습니다. 그냥 글배[3], (글접+3)^ 만 가능하고, 이것을 C처럼 바꿔쓸 수는 없습니다. 왜냐하면 배열과 접근형은 애초부터 그 뿌리가 다른 자료형이기 때문입니다.
 

2) 함수(절차)의 실인자로 배열을 전달할 수 있다.

다음을 봅시다.
 
int sum( int data[10] ) { int total = 0, i; for ( i = 0; i < 10; i++ ) total += data[i]; return total; }
절차 합계구하기( 정수[10] 자료 ) => 정수 정수 합계 := 0, 가. { 준비 가 := 0. 조절 가++. 되풀이 가 < 10 참인 동안 { 합계 += 자료[가]; } 합계 넘긴다. }
 
이런 바탕글이 있다고 합시다. 앞에서 말한바와 같이 C에서는 편법을 쓰지 않는 이상 함수에 배열 변수 그 자체를 전달할 수 없습니다. 그저 그 배열 변수의 주소값만 전달할 뿐이지요. 사실 함수 선언의 "int data[10]"이란 것 역시 의미가 없고 내부적으로는 "int *data"와 같이 처리됩니다. 그래서
 
int a[10], b[20], i; ... i = sum( a ); i = sum( b );
 
와 같이 해도 전혀 잘못될 것이 없습니다. 왜냐하면 어차피 접근형으로 넘어가면 배열의 "크기" 따위는 함수에 전달할 수가 없기 때문이지요. 하지만,
 
정수[10] 하나. 정수[20] 두울. 정수 가. ... 가 := 합계( 하나 ). 나 := 합계( 두울 ). // 잘못!
 
씨앗에서는 위와 같이 서로 다른 자료형의 배열을 그냥 전달하게 되면 위와 같이 잘못이 일어나게 됩니다. 왜냐하면 씨앗에서는 배열 변수 자체를 절차에 전달할 수 있게 되고, 자연히 그렇게 되면 배열 변수의 크기까지 전달이 되기 때문이지요. 자, 어떻습니까? 어느 쪽이 더 논리적이고 합리적인지, 또 어느 쪽이 벌레를 만들 가능성이 더 많은지는 여러분들 판단에 맡기지요.
 

3) 배열 이름 자체는 오직 배열일 뿐이다.

씨앗에서는 C에서처럼 배열 이름이 접근형으로 변하는 해괴망측한 일이 (사용자가 지시하기 전 까지는) 결코 일어나지 않습니다. 배열은 그저 배열일 뿐입니다. 위에서 제가 작성한 바탕글을 다시 보십시오. 합계구하기()에 전달한 인자는 접근형이 아니라 어엿한 배열입니다. 실재로 이 작업이 이루어지면 기억 공간에 '하나'의 내용을 복사하여 합계구하기()의 형식인자인 '자료'에 담습니다. 즉 배열을 완벽하게 전달할 수 있지요. 그러나 C는 안됩니다. 왜냐하면 배열을 배열로 전달하고 싶어도 이미 배열 이름 그 자체가 어느새 접근형이 되어버리기 때문이지요.
 
이제 씨앗이 배열과 접근형을 어떻게 구별하는지를 아셨을 것입니다. 불편해 보인다구요? C의 방식이 더 편하다구요? 하하하~ 정말 그럴까요? 그럼 다음을 봅시다.
 
절차 합계구하기( &정수[] 자료 ) => 정수 정수 합계 := 0, 가. { 준비 가 := 0. 조절 가++. 되풀이 가 < 원소수(자료) 참인 동안 { 합계 += 자료[가]; } 합계 넘긴다. }
 
위의 바탕글은 C언어처럼 배열 변수의 원소 개수에 제한을 받지 않고 처리할 수 있는 방법입니다. 바로 말씀하신 "열린 배열"을 쓰는 것이지요. 아직은 씨앗에서 열린 배열을 가리킴 전달(call-by-reference: 흐흐~ C에서는 이런걸 사용자가 챙겨줘야하지요?)로 밖에 전달할 수 없지만, 그래도 배열변수 자체가 절차로 전달되는 것은 변함이 업습니다. C와 같이 접근형이 전달되는 것이 아니라 배열 변수가 전달되는 것이므로, 당연히 전달되는 배열 변수의 크기를 알 수 있지요. 원소수()가 바로 그러한 연산자입니다. 바탕글을 보시면 아시겠지만, 열린 배열로 된 배열은 원소수()를 써서 얼마든지 배열 원소의 개수를 구할 수 있지요. 배열을 통째로 전달한다는 것이 얼마나 편한지 이젠 아시겠습니까? 흔히들 강력하다고 하는 C언어가, 씨앗에서 쉽게 할 수 있는 위와 같은 일을 할 수 없다는 것을 알고 계시는지....
 
 
이야기가 다른 쪽으로 많이 빚나갔는데... 다시 답변으로 돌아옵시다. 답변에서 문제가 되는 것은, 글줄 상수―정확히는 글자 배열 상수―를 C와 씨앗에서 어떻게 다루느냐 하는데 있는 것 같습니다.
아시는 바와 같이 C에서의 글줄 상수는 글자의 접근형(char *)입니다. 그래서, cp = "I am a boy"; 와 같은 문장이 가능하게 되지요. 하지만 씨앗에서의 글줄 상수는 크기가 글자의 길이+1 인 글자 배열(글자[길이+1])입니다. 그러 므로 C라면..
 
cp = "I am a boy."; // o ca = "I am a boy."; // x
 
이지만.. 씨앗이라면...
 
글접 := "나는 소년이다.". // x 글배 := "나는 소년이다.". // o
 
가 되는 것입니다. 즉 씨앗의 글줄 상수의 자료형은 바로 배열입니다. 그러므로, 글자의 접근형이 글자의 배열(글줄 상수)을 가리키게 하려면, 글줄 상수의 주소를 얻어서 대입해 주면 되는 것이지요. 그러니까
 
글접 := &"나는 소년이다.". // o
 
와 같이 하면 되는 것입니다. 즉 자료형이 배열인 글줄 상수의 주소값을 얻어서 그것을 글자의 접근형에 넣어주는 것이지요. 이렇게 되면 아무런 문제가 없이 C에서와 같은 일을 할 수 있지요.
자, 이번엔 질문자 님이 예로 드신 걸 봅시다.
 
char *command[] = { "north", "east", "south", "west", "\n" }
 
여기서 질문자 님은 글자 접근형의 배열을 사용하려고 하셨습니다. 근데 질문자 님 글에서 "배열 포인터"라고 하셨는데, 잠시 헷갈리셨나봅니다. 위와 같은 경우는 배열 포인터(array to pointer)가 아니라 포인터 배열 (array of
pointer)이지요. 접근형이 다섯 개 모인 배열 변수를 의미하는 것이지요. 처음에 변환하신
 
글자^[5] 명령어 := { "동", "서", "남", "북", "\줄" }.
 
은 철저히 C의 패러디임(paradigm)으로 쓰신 것이지요. 앞에서 말했듯이 씨앗은 C가 아닙니다. 위와 같이 하면 당연히 안 되지요. 늘 말씀 드렸지만, 씨앗의 글줄 상수는 배열입니다. 접근형이 아니지요. 접근형을 만들기 위해서는 주소값이 필요합니다.
 
글자^[5] 명령어 := { &"동", &"서", &"남", &"북", &"\줄" }.
 
이렇게 하면 문제는 끝난 거지요. 어렵게 생각할 필요가 없습니다. 씨앗의 글줄 상수가 접근형이 아닌 배열이라는 사실만 아셨더라도, 그리 어렵게 생각되지는 않으셨을 것입니다.
이제 글줄―정확히는 배열―을 접근형으로 다루는 방법은 이해하셨으리라 믿습니다. 간단하지요? 그냥 배열 변수 이름 앞에 주소 연산자 "&"를 덧붙이면 됩니다. 이제는 반대로, 접근형을 배열처럼 다루어 봅시다. 우선 다음의 바탕글을 보시기 바랍니다.
 
#include <stdio.h> #include <stdlib.h> void main( void ) { char (*)(cp[80]); int num, i; puts("How many man? =>"); scanf("%d\n", &num ); cp = (char *)calloc( sizeof(char[80]), num ); if (cp == NULL) exit(1); for (i = 0; i < num; i++) { printf("#%d=> ", i+1); gets( cp[i] ); } for (i = 0; i< num; i++) printf("#%d: %s\n", num+1, cp[num]); free( cp ); }
시험 단원. 사용 단말흐름, 바탕. 절차 시작점(). 구현부 절차 시작점() 글자[80]^ 글접. 정수 사람수, 가. { 글줄쓰기("몇 명이지요?=> "). 정수읽기( 사람수 ). 입력버리기(). 글접 := 배열얻기( 크기값(글자[80]), 사람수 ). 글접 = 공백 { 참이면 마치기(1). } 준비 가 := 0. 조절 가++. 되풀이 가 < 사람수 참인 동안 { 틀짜기("#%십=> "). 정수쓰기( 사람수+1) ). 글줄읽기( 글자[80](글접+가) ). // ### } 준비 가 := 0. 조절 가++. 되풀이 가 < 사람수 참인 동안 { 틀짜기("#%십: %줄\줄"). 정수쓰기( 사람수+1 ). 글줄쓰기( 글자[80](글접+가) ). // ### } 내놓기( 글접 ). }
 
위의 바탕글을 잘 보시기 바랍니다. 이 중 ### 표시를 해 둔 부분이 바로 접근형을 배열처럼 다루는 예입니다. 앞에서 배열을 접근형으로 다루려고 할 때에는 우선 &를 이용해서 주소를 얻는다고 했습니다. 반대로 접근형을 배열 처럼 다루고 싶을 경우에는, 다루고 싶은 접근형을 배열 자료형으로 자료형 변환(type casting)을 하면 됩니다. 글줄읽기()글줄쓰기()는 모두 글자의 열린 배열을 받습니다. 그러므로 C와 같이
 
puts( cp[i] ); 글줄쓰기( 글접[가] ). // (1)
 
혹은...
 
글줄쓰기( 글접+가 ). // (2)
 
라고 하면 잘못이 생깁니다. 왜냐하면, (1)은 접근형을 배열처럼 다루었기 때문에 틀리는 것이고, (2)는 형식인자는 배열인데 실인자가 접근형이라서 생기는 자료형의 불일치입니다. 그러므로
 
글줄쓰기( 글자[80](글접+가) ).
 
와 같이 해야 하는 것입니다. 즉 간단히 말하면..
 
char *p; ... p = "C is very good language."; puts( p );
글자^ 접. ... 접 := &"씨앗은 좋은 언어입니다.". 글줄쓰기( 글자[80](접) ).
 
과같이 해야 합니다. 즉 반드시 접근형을 배열로 변환해서 다루어야 한다는 것이지요. 배열로 바꿀 때 크기는 상황에 따라 정해주면 됩니다. 크기를 모를 경우는 32767 정도로 해 줘도 상관이 없습니다. 왜냐하면 크기가 크기도 해서 실재로 기억공간을 많이 차지하는 게 아니라, 단지 번역기에게 대상물의 크기만 알려줄 뿐이기 때문입니다.
 
요컨대, C에서는 배열과 접근형을 굳이 구분하여 생각할 필요가 없지만, 씨앗에서는 배열과 접근형을 엄격히 구별하여 사용해야 하고, 둘을 섞어서 쓸 때는 주소값 연산자"&"나 강제 형변환을 이용해야 한다는 것입니다.
덧붙이면, 씨앗은 접근형이면 반드시 주소 연산자가 따라다녀야 합니다.(여기서 구조물 접근형 변수는 제외합니다.) 다음을 보십시오.
 
char c, ca[10], *cp; typedef void (*)(func)(); func fp; // 함수의 접근형(pointer to function) void test(void); free( cp ); // 접근형 자체를 썼으므로 &는 당연힌 불필요. free( ca ); // 접근형이 아니지만 &를 안 붙임. 헷갈리기 쉬움. fp = test; // 역시 함수이름 자체가 접근형이 됨. 말도 안됨! fp(); // 간접연산자 없이 부를 수 있음. 역시 말도 안됨!
글자 글. 글자[10] 글배. 글자^ 글접. 자료형 절차접근형 = 절차^(). 절차접근형 절접. 절차 시험하기(). 내놓기( 글접 ). // 접근형 자체를 썼으므로 &는 당연히 불필요. 내놓기( &글배 ). // 배열이니까 당연히 &를 붙여서 접근형을 만듦. 절접 := &시험하기. // 절차 주소를 얻기 위해 &를 씀. 논리적임. 절접^(). // 절접이 가리키는 절차를 부르기 위해 ^를 꼭 씀.
 
자, 위의 경우들을 비교해 봅시다. 과연 어느 쪽이 더 눈에 금방 들어오고, 혼동의 우려가 없으며 논리적인지를 잘 보십시오. 절대로 편견에 사로잡히지 말고, 있는 그대로를 보십시오. 그리고 다시 한번 생각해 보세요. 뭐가 어떻게 다르고, 뭐가 좋고 뭐가 잘못되었는지를...
 

3. 배열 크기의 생략

질문
배열에서요.... 씨에선 위 2 번 질문의 예제처럼 command[] 해 놓으면 자기가 알아서 크기를 잡자나요... 근데 씨앗에선 왠 에러문...혹시 꼬진거 아닐까요? 헤헤~~~ 이거 저렇게 자동감지 할 수 있는 방법 아심 푸헤헤...
 
답변
다시 한번 말씀 드립니다. 씨앗은 C가 아닙니다. 애초부터 씨앗을 설계할 때 배열의 처음값을 줄 때 크기를 생략할 수 없었습니다. 이는 언어를 설계한 사람의 취향이라고나 할까요... C를 쓴 사람은 생각 자체가 지저분하고 헐렁헐렁합니다. 그래서 최대한 생략할 수 있게끔 해 놓았지요. 그러나 씨앗은 상당히 논리적으로 설계가 되어 있습니다. 물론 이 두 가지―크기를 생략할 수 있느냐와 없느냐―가 서로 각각 장단점이 있습니다. 제가 이에 대해서 나눔기술에 건의했었는데, 개발 팀에서는 별로 권하고 싶지 않은 C의 문법이라서 일부러 자동 감지하는걸 뺐다고 합니다. 하지만 사용자가 좀 더 사용하기 편해야 하고, 또 너무 딱딱한 것이 좋지 않으니, 씨앗의 다음 판에서는 질문자 님이 말씀하신 크기값 생략 기능이 들어갈 것이라고 하더군요.
그런데 말입니다.... 씨앗에서 크기값을 생략할 수 없다고 "혹시 꼬진 거아닐까요?"라는 말씀을 하셨는데... 글쎄.. 이게 장난으로 하신 말인지 진담인지는 모르겠지만, 만약 진짜로 씨앗이 C보다 꼬저보인다면 그건 문제가 많습니다. 오히려 그렇게 따지면 C가 씨앗보다 더 꼬집니다. 위에 말했듯이, 씨앗과 C를 비교할 때, 씨앗은 할 수 있는데 C가 못하는 것이 너무 많습니다. 이렇게 말하면 "씨앗에서는 인트럽트나 렘상주 프로그램을 못 만든다"라고 반박하실 수 있지만, 이런 건 어디까지나 환경 의존적인 차원이지요. 말씀하신 것들은 C의 기본 사양이 아니라는 말입니다. 이런 환경적인 측면들은 씨앗이 발전됨에 따라 앞으로 얼마든지 기능을 확장해 나갈 수 있지요. 그러나 C를 보세요. C에서 배열변수를 통째로 실인자로 넘길 수 없는 건, 하늘이 무너져도 해결할 수 없는 문제입니다. 왜냐하면 그건 애초부터 C언어를 설계한 사람의 생각이 잘못되었던 것이기 때문이지요. 또 열린 배열을 사용해서 자동으로 배열의 크기를 알 수 있는 원소수()같은 것도 C에는 존재하지조차 않습니다. 바탕글을 입력할 때 크기를 감지하는 게 편하겠습니까, 실재로 풀그림이 실행할 때 배열의 크기를 감지해 주는 게 더 편하겠습니까? 판단은 독자들이 하십시오. 그럼, 이렇게 보면 씨앗이 꼬집니까, C가 꼬집니까? 역시 판단은독자들이 하시길...
 

4. #define 지시어에 대해서...

질문
C 에선 define 문이 있자나요. 씨앗에선 없나요? 못 찾겠던데... 상수 란게 있긴 있던데... 그건 한번 상수 선언하면 undef 처럼 풀어지지도 않고.. 함수들의 define 도 못하자나요... 혹시 그게 할 수 있으면 가르쳐 주시고...
 
답변
우선 결론부터 말씀드리면 C의 #define 전처리기 지시어와 같은 기능이 씨앗에서는 제공되지 않습니다. 그런데 #define이나 #include 같은 지시어들은 원래부터 C언어만이 가지고 있던 기본 문법이 아닙니다. 이렇게 들으시면 의아해 하실 지 모르겠는데, 이것은 엄연한 사실입니다. 앞에 #이 붙은 것은 번역기가 아닌 전처리기에 지시를 내리는 것이지요. 예전의 C언어 번역기들은 모두 2과정(pass)으로 바탕글을 번역했습니다. 우선 전처리기가 바탕글을 훑고, 다음 후처리기가 바탕글을 훑어서 기계어를 만들어내지요. 이 때 전처리기가 #define, #include#if, #else, #elseif, #endif#pragma 등과 같은 지시어를 만나면 전처리기에서 특별한 처리를 해서 바탕글을 가공합니다. 그러므로 이런 전처리기 지시어들은 기본적으로 C의 문법에 들어가지 않는 것이지요. 이를 태면 Macro Assembler에서도 이와 같은 전처리기 지시어들이 상당수 존재하고, 요즘 나오는 터보 파스칼에서도 {$IF} 등과 같은 번역기 지시어(터보 파스칼은 1과정으로 번역이 됩니다.)가 존재합니다. 하지만 이러한 지시어들은 지시어일 뿐이고, 실재로 언어의 문법과는 거리가 멉니다. 역시 제가 #define과 같은 전처리기 지시어를 씨앗에 넣을 것을 건의하자, 나눔기술에서는 역시 씨앗의 다음 판에 이런 기능을 넣도록 하겠다고 말을 하시더군요.
그런데.... 정말 #define이 필요할까요? 물론 반드시 필요할 때도 있습니다. 그러나 우리들이 보통 사용하는 상수의 정의나 간단한 함수의 정의와 같은 경우는 #define이 아니라도 충분히 할 수 있습니다. 질문자 님은 #define으로 정의된 상수는 #undef로 풀어줄 수 있다고 하셨습니다. 네. 맞는 말씀이지요. 그러나, 왜 #undef 문이 생기게 되었는지에 대해 생각해 보신 적이 있으신가요? 그것 역시 (질문자 님 표현을 빌리자면) C가 꼬지기 때문입니다.
C에서는 어떤 이름이 전역 적으로 알려지게 되면 그런 이름들은 모든 바탕글에 영향을 미칩니다. 즉 aaa.c, bbb.c라는 바탕글이 있는데, aaa.c에서 test()라는 이름으로 함수를 만들었다고 가정해 봅시다. 그럼 이 test()란 함수는 (static으로 선언되지 않는 이상) 모든 바탕글에 알려지게 되고, 그로 인해 test()라는 함수는 전체 풀그림을 통틀어 한번밖에 나올 수 없습니다. 왜냐하면 C언어는 이름을 전체적으로 관리하기 때문입니다.
그러나 씨앗은 다릅니다. 씨앗은 '단원'이란 개념이 있습니다. 이름 역시 이런 단원별로 분리가 되어 있습니다. 가령 '가가' 단원에서 시험하기()란 절차를 만들었다고 해도, '나나' 단원에서도 역시 아무런 제한 없이 시험하기()를 만들 수 있습니다. 다만 '가가'나 '나나' 단원을 사용하는 상급 단원은, 두 단원에 같은 이름이 있으므로 가가->시험하기(), 나나->시험하기()와 같이 구분을 해 줄 필요가 생기게 되는 것이지요.
즉 C의 경우 한번 정의된 이름은 계속 그 효력을 발휘하기 때문에, #define으로 지은 이름만이라도 해제할 수 있도록 배려한 것입니다.
또, #define으로 매크로 함수를 작성하는 것에 대해서 말씀하셨습니다. 이것은 상당히 편리한 기능이지요. 한 두 줄 짜리 함수를 쓰는데는 오히려 보통의 함수를 쓰는 것 보다 #define으로 매크로 함수 만들어 쓰는 게 속도가 빠르지요. 하지만, #define에는 치명적인 단점이 있습니다. 이를 태면..
 
#define sqr( a ) (a)*(a)
 
와 같이 add 매크로를 만들었다고 합시다. 그리고..
 
int a = 3, b; b = sqr(++a);
 
 
와 같이 했다고 생각해 봅시다. 언뜻 보면 a는 4가 되고, b는 1이 더해진 a를 두 번 곱했으므로 16이 된다고 생각할지 모릅니다. 물론 바탕글을 보면 그럴듯하지만, 실재로 a는 5가 되고, b는 20이 됩니다. 왜 그럴까요? 매크로를 하나 하나 전개해 보세요... 그럼 결과적으로..
 
b = (++a) * (++a);
 
가 되겠죠? 이런 매크로의 단점들이 있지요. 그래서 C++에서는 모양은 함수의 꼴을 하고 있지만, 실재로 매크로 함수와 같이 동작하는 '줄무리 함수'(inline function)를 제공하지요. 저는 씨앗의 다음 판에서 #define 같은 거 보다 오히려 이런 '줄무리 함수'를 만들 수 있는 기능이 추가되는 게 더 바람직하다고 생각되어집니다.

5. 번역기 내부에 살고 있는 벌레

질문
그 때 책에도 없는 1045 번 에러 생긴다는... 그거 바탕글입니다.
 
land 단원. 사용 글줄. 상수 최대착용 = 18. 상수 중성 = 0. 상수 남성 = 1. 상수 여성 = 2. 자료형 졸개자료 = 구조물 { 인물자료^ 졸개. 졸개자료^ 다음. }. 자료형 인물자료 = 구조물 { 작은정수 번호. 작은정수 위치. 인물자료^ 다음싸울. 졸개자료^ 졸개들. 인물자료^ 대장. }. 절차 시작점(). 절차 이동명령(&인물자료 인물, &바이트정수 인수). 구현부 절차 시작점() { } 절차 이동명령(&인물자료 인물, &바이트정수 인수) { }
 
최대한 줄여볼라고 했는데..이제 다 입니다. 이거 고대로 컴파일 하심 에러문 보실 수 있을꺼예요. 1045 번 에러요.. 신기하죠? 원래는 절차 시작점()줄과 그 밑줄이
 
사용 글줄. 줄의 밑에 있던건데...
 
거기에 두면 또 무슨 선언을 안했다는....쩝 그래서 이동을 시켰더니... 어찌됐건 그런 에러문이 생겼다는게 문제니깐...
 
답변
저도 심상치가 않아서 바탕글을 직접 번역해 보았습니다. 말씀하신 것 같이 알 수 없는 잘못 알림글이 나오는군요. 대강 문법적으로 보았을 때 틀린 부분은 없는 것 같았습니다. 그런데.... 아마 앞쪽 가리킴(forward reference)을 처리하는 과정 중에 번역기 내부에 살고 있던 벌레가 활동을 한 모양입니다. 이 바탕글을 나눔기술로 보내보세요. 그러면 나눔기술의 확답이 있을 것이고, 운이 좋으면 벌레를 피해갈 수 있는 길을 알려줄 수도 있습니다. 만약 그런 길이 없다면 씨앗의 다음 판에서는 반드시 이 벌레가 잡힐 것입니다. 이 사실을 기억하시고, 바탕글을 꼭 나눔기술에 보내보세요. 보내는 게 마음에 걸리신다면 제가 직접 보내지요.
 

마지막. 씨앗에 대한 여러 생각

질문
왜 자꾸만 씨앗이 부실해 보이죠? 흐~~~~ 씨앗에서 한글 나온다는 거하구, 세벌식 된다는거 빼곤... 하긴 C 가 너무 좋은거일지도... 다른 언어하고 비교해도 C 가 더 좋다고 할 수 있으니... 씨앗에서도 마우스 지원하고 다중창 지원도 했으면 좋겠네요. 흐~~~ 많이도 썼군요... 나눔에 전화 해 보니 씨앗 담당자님이 휴가가셨대요... 어떤 여자분이 받으시던데... 히히~~~~ 으음... 어쨌든 저의 질문에 대답해 주셨던 분들... 시원한 여름 보내세요. 그럼 감사...
 
답변
질문자 님께서 씨앗이 꼬저인다고 하셨습니다. 정말 그럴까요? 자, 여기 두 대의 자동차가 있습니다. 하나는 보통 변속기를 단 자동차이고, 나머지 하나는 자동 변속기를 단 자동차입니다. 보통 변속기를 단 차를 타고 다니던 사람이 친구의 소개로 자동 변속기 차를 운전하게 되었습니다. 그런데 어라? 차가 가다가 자꾸만 멈춥니다. 보통 변속기를 운전할 때처럼 기어를 넣어봅니다. 그러나 생각대로 잘 안됩니다. 클러치와 엑셀레이터 역시 내가 생각한 데로 듣지 않습니다. 그러면서 말합니다. "이 친구야. 자네 차 정말 꼬졌군!"
자, 객관적으로 생각해 봅시다. 늘 보통 변속 차량만 운전하던 사람이 자동변속기가 달린 차를 운전할 때에는, 당연한 자동 변속기에 대한 사전 지식이 불충분했을 것이고, 각 상황에 대처하는 방법도 몰랐을 것이며, 기술이 서투른 것은 너무나 당연한 것입니다. 자동 변속 장치를 만져본 경험이 없는 그는 지금 차가 가지고 있는 기능의 40%도 쓰지 못하고 있습니다. 아니 쓸 줄 모르고 있습니다. 그러면서 애꿎은 자동차에만 화를 버럭버럭 내고 있습니다. 보통 변속기와 자동 변속기는 똑같이 '기어'라는 것을 사용하지만, 그 쓰임새와 사용 방법, 쓰는 기술, 사용되는 개념이 다릅니다. 그런 걸 무시하고 기존에 자신이 가지고 있던 생각을 억지로 끼워 맞춰 가면서 운전을 하니, 제대로 될 리가 없지요.
이제 이야기를 C와 씨앗 쪽으로 돌려보겠습니다. 질문자 님이 늘 C를 쓰셨으니 당연히 C에 익숙할 것입니다. 그리고 씨앗과 C가 가지고 있는 공통점 역시 금방 발견할 수 있을 것입니다. 그러나 두 언어 사이의 비슷한 점을 제대로 배워두지 않고, C를 다루던 방식 데로 씨앗을 다룰 경우에는 잘못이 생기는 건 당연한 것입니다. 자신이 씨앗을 C의 시각으로 바라보아서 생기는 문제에 대해서는 생각하지 않고, 무조건 자신의 생각대로 안된다고 하나의 언어를 평가 절하하는 것은 결코 옳은 일이 아니라고 저는 생각합니다.
씨앗이 정말 그렇게 부실해 보입니까? 씨앗 언어 설명서에 있는 언어 정의를 보시기 바랍니다. 오히려 헐렁헐렁하게 짜깁기 식으로 정의된 C언어보다 씨앗이 체계적이면 체계적이지, 더 못하지는 않습니다. 저는 그렇게 믿고 있고 확신합니다. 제가 직접 체험했기 때문입니다. 씨앗이 꼬진 것이 아니라, 질문자 님께서 현재 씨앗의 기능을 충분히 활용하고 계시지 못한 것이 아닐까요? 다시 한번 생각을 바꾸어 보십시오. 씨앗에서 한글이 나오고, 세벌식을 지원한다는 것이 별것 아닌 듯 하십니까? 하지만 이게 '별것 아닌 것'이 아닙니다. C로 한글을 내장한다고 생각해 보십시오. 바탕글은 어디서 입력을 합니까? 또 그 어렵고 지겨운 한글 자료 처리를 어떻게 다 공부하실 것입니까? 세벌식의 지원 역시 얼마나 편합니까? 다시 한번 생각을 바꾸어 보십시오.
질문자 님께서는 "하긴 C가 너무 좋은 것 같다. 다른 언어와 비교해도 C가 더 좋다고 할 수 있다"고 하셨습니다. 하지만 제 생각은 다릅니다. 어떤 언어를 다른 언어와 비교하면서 "C는 씨앗보다 좋다", 혹은 "씨앗은 C보다 좋다"라고 딱 꼬집어 이야기할 수는 없습니다. 왜냐하면, 각 언어들의 설계 목적이 다르고 지향하는 바가 다르며, 그렇기 때문에 각 언어마다 서로 여러 가지 장단점이 있는 것입니다. 이를 태면 C라면 다른 언어에서는 따라오기 힘든 많은 자료들과 라이브러리를 갖고 있지만, 한편 다른 언어들보다 떨어지는―꼬진―부분도 상당히 많습니다. 씨앗 역시 그렇습니다. 한글을 처리할 수 있고, 생각한 것을 그대로 나타낼 수 있으며, C와 같이 시스템 쪽으로도 접근이 가능하다는 면에서는 C언어보다 강력하고 베이직보다 편하지만, 아직까지 렘상주풀그림을 만든다거나 인트럽트 처리기를 만들 수 없고, 줄 무리 조립기(inline assembler)가 지원되지 않는다는 취약점이 있습니다.
이 세상에 완전한 것이란 없습니다. 정말 말씀하신 것처럼 다른 언어를 비교해 보았을 때 C가 월등하게 뛰어납니까? 저는 '아니오'라고 답하고 싶습니다. 우선 C언어에서는 다른 언어에서 다 지원하는 '가리킴 전달'(call bye reference)을 지원하지 못합니다. 또한 위에서 말했듯이 함수의 실인자로 배열 변수를 넘길 수 없습니다. 또 씨앗과 같이 단원이란 개념이 없으므로 큰풀그림을 짜는데 비효율적인 면을 보입니다. 한가지 예를 더 들어볼까요?
 
정수 가. ... 가 값이 { 1=> 이거하기. 2, 3, 4=> 그거하기. 100=> 인사사기. |11=> 소개하기. |12=> 마치기. 그박=> 잘못알리기. }
 
이런 거.. C나 파스칼에서는 도저히 할 수 없는 기능입니다. 우선 값이 1일 때에는 이거하기() 절차를 부르고 선택문을 빠져나갑니다. C와 같이 break문이 필요 없습니다. 또 값이 2, 3, 4일 때는 그거하기()절차를 부릅니다. 이것은 꼭 파스칼에서 여러 값을 나열해 주는 것과 같고... C에서 각각의 casebreak로 자르지 않은 것과 비슷합니다. 또 값이 10이면 인사하기(), 소개하기(), 마치기()를, 11이면 소개하기(), 마치기(), 12이면 마치기()를 실행하는 경우에는 C에서
 
case 10: hello(); case 11: intro(); case 12: quit()
 
처럼 각 casebreak로 나누지 않은 것과 같은 역할을 합니다. 제가 지금까지 다루어 본 언어―베이직, 파스칼, C, 씨앗, XBase 등등―중에 씨앗만큼 값 선택문이 유연한 언어를 보지는 못했습니다. 이런 면에서는 씨앗이 그 어떤 언어보다 우수하고 탁월합니다.
물론 씨앗 통합 환경이 다른 언어의 그것들보다 기능이 떨어져서, 즉 말씀하신 마우스나 다중 창의 지원과 같은 것을 아직까지 지원하지 않아서 씨앗을 혹평하실 수도 있습니다. 하지만 잘 생각해 봅시다. 이런 것은 어디까지나 환경적인 문제이며, 개선될 여지가 충분히 있습니다. 또 씨앗이 지금 1.0판이라는 것을 생각한다면, 앞으로 많은 판올림이 있을 것은 당연한 것이지요. 이런 환경이 지금은 열악하고 해서, 씨앗 언어 자체가 다른 언어에 비해 기능이 떨어진다거나 부실하다고 생각되지는 않습니다. 오히려 전 C언어가 더욱 더 부실하다고 생각됩니다. 왠지 성수 대교 위를 지나고 있는 것 같은 ... 언제 어디서 벌레가 툭툭 튀어나올지도 모르는.. 그런...
저는 말하고 싶습니다! 우리들이 언제부터인가 남의 나라 것은 비판 없이 받아들이며 좋은 것으로 잘못 생각하고, 우리의 것은 사실과는 다르게 그 품위나 위상, 혹은 기능들을 낮추어 보는, 그런 사대주의적인 생각을 하고 있는 것은 아닌지... 여러분! 생각해 보십시오. 우리들이 언제까지 남의 나라말로 풀그림 짜고, 언제까지나 남의 나라 기술에 종속되어 있어야 합니까! 인공위성 무궁화호가 정말 우리의 손수 기술로만 만들어진 것입니까? 아니지 않습니까.. 그러나 무궁화호가 주목을 받는 이유는, 거기에 조금이나마 우리의 기술이 들어가 있기 때문입니다.
씨앗도 바로 그러한 작은 '씨앗'이 되기 위해 만들어졌습니다. 지금은 환경이 부실하고, 최적화도 덜 되고, 다른 언어에 비해 부족한 자료와 단원묶음(library)때문에 고전하고 있지만, 이런 악조건 속에서도 땀을 흘리며 품종을 개발하는 나눔기술, 그리고 박토를 옥토로 바꾸기 위해 지금도 애쓰는 우리 농사꾼들이 있는 이상, 씨앗이 결코 말라죽거나 사람들의 기억 속에 추억으로만 남는 언어가 되지는 않을 것입니다.
우선 질문자 님부터, C언어로 갈고 닦은 '감'을 씨앗으로 전향하여, 씨앗을 좀더 체계적으로 배우고, 그래서 씨앗으로 좋은 풀그림을 만든다면, 그것 역시 우리 나라 무른모 산업을 발전시키는 하나의 씨앗이 되지 않을까요?
상당히 길게 글을 썼는데, 지루하시리라 생각됩니다. 하지만 요 몇일세 저 또한 씨앗에 대한―아니, 씨앗의 환경에 대한 어느 정도의 회의를 가지고 있었습니다. 하지만 질문자 님을 위해 답변의 글을 적으면서, 저의 그런 회의적인 마음이 다시 새로운 정렬로 끓게 되었습니다. 씨앗.. 결코 포기할 수 없는 우리의 재산이며 기술입니다. 그리고 혹시 글 중에, 질문자 님의 기분을 상하게 하는 말이 들어있다면 이 자리를 빌어 진심으로 사죄의 말씀을 전합니다. 저는 질문자 님을 깎아 내리거나 개인적인 감정이 있는 것이 절대로 아님을 알아주세요.
또 다른 분들 역시 이 글을 읽으면서 씨앗이 절대 다른 언어에 뒤지지 않는다는 것을 깨달아 주셨으면 합니다. 그럼 여기서 긴 글을 닫겠습니다.

 
👉 다음 글:
뱀놀이 예제