back

비좌변값 배열(non-lvalue array) #1

11년 전 작성

이 글은 시리즈 글의 일부입니다.


C 언어에서 배열(array)은 이른바 "1급 시민(first-class citizen)"이 아닙니다. 프로그래밍 언어 이야기를 하다말고 "시민"이라는 단어가 나와 무엇인가 하겠지만, 프로그래밍 언어에서 "1급 시민"인 데이터형(data type)은

  • 변수(variable, object)에 저장될 수 있고
  • 함수(function)나 서브 루틴(sub-routine)에 매개변수(parameter)로 전달할 수 있고
  • 함수의 결과로 반환할 수 있고
  • 실행 중에 동적으로 생성할 수 있고
  • 기본적인 연산(operation)이 잘 정의되어 있는

등의 특징을 갖는 데이터형을 말합니다 - 책마다 사람마다 서로 조금씩 다른 의미로 사용하므로 "감"으로 느끼는 것이 중요합니다. 예를 들어, 포인터 형(pointer type)의 경우 아래 코드처럼 변수에 저장하거나 동적으로 생성할 수 있고

int i, *p = &i, *q = p;

함수에 매개변수로 전달할 수 있고

extern void f(int *);

함수의 결과로 반환할 수 있고

extern int *g(void);

기본적인 포인터 연산이 잘 정의되어 있으므로 "1급 시민"에 해당하는 데이터형입니다. 문자형(character type), 정수형(integer type), 부동형(floating-point type) 등도 마찬가지지요.

반면, 함수는 어떨까요? 다른 언어와는 달리 C 언어는 함수도 데이터형의 일부로 다루며 함수를 변수에 저장하는 것부터 불가능(함수 포인터 pointer to function 에는 함수를 가리키는 포인터가 저장되는 것이지 함수가 저장되는 것이 아닙니다!)하므로 "1급 시민"이 아닙니다. 물론, 함수를 매개변수나 반환형으로 직접 쓰는 것도 불가능하고(모두 포인터로만 가능합니다) 더군다나 동적으로 생성하는 것은 상상조차 하기 어렵습니다.

참고로 함수형 언어(functional language)나 자바스크립트(JavaScript) 같은 언어에서는 함수를 변수에 저장하는 것도, 매개변수나 반환값으로 주고 받는 것도 심지어는 동적으로 생성하는 것도 가능해 함수에 "1급 시민"의 지위를 줍니다.

그럼, 배열과 구조체(structure)는 어떨까요? 배열과 구조체는 둘을 묶어 집합형(aggregate type)이라고 부를 만큼 비슷한 특성(둘의 차이점은 배열은 같은 데이터형만 요소형으로 갖고, 구조체는 서로 다른 데이터형을 멤버형으로 가질 수 있다는 점입니다)을 가지고 있지만 하나(배열)는 "1급 시민" 근처에도 못 가지만, 다른 하나(구조체)는 거의 "1급 시민" 대우를 받는 차별이 있습니다.

구조체에 대한 기본적인 연산이 잘 정의되어 있지는 않으므로(예를 들어, 구조체를 ==로 비교하는 것이 불가능하죠) "1급 시민"에 조금 부족하지만 변수에 저장도 가능하고 심지어는 함수 매개변수나 반환값으로 전달하는 것이 가능합니다. 다시 말해 "구조체형 값"의 개념이 존재하는 것입니다. 하지만, 배열은 함수와 마찬가지로 거의 모든 문맥에서 포인터로 변환("decay"된다고 표현합니다)되기에 배열형 변수에 직접 배열을 대입하는 것, 배열을 매개변수나 반환값으로 주고 받는 것이 불가능합니다.

사실, 초기 C 언어에서는 구조체도 배열과 다른 대우를 받지 않았습니다. 지금의 배열과 마찬가지로 함수에 직접 전달할 수 없어 포인터를 사용해야 했고, 반환 역시 마찬가지였습니다. 이후 여러 컴파일러에서 구조체를 "1급 시민"에 준하는 데이터형으로 지원하기 시작했고 1989년에 발표된 ANSI 표준에서 처음 "공식적으로" 구조체 값을 지원하기 시작했습니다.

이미지

이 사진은 흔히 K&R 혹은 K&R1 이라고 부르는 1978년 출판된 "The C Programming Language" 1판을 촬영한 사진입니다. 구조체를 함수와 사용할 때 가해지는 제약에 대해 설명하고 있으며 이미 저 시점에도 이후 구조체에 가해지는 제약이 완화될 것임을 명시하고 있습니다.

아래는 모바일 배려...

There are a number of restrictions on C structures. The essential rules are that the only operations that you can perform on a structure are take its address with &, and access one of its members. This implies that structures may not be assigned to or copied as a unit, and that they can not be passed to or returned from functions. (These restrictions will be removed in forth-coming versions.) Pointers to structures do not suffer these limitations, however, so structures and functions do work together comfortably.

아래는 친절한 번역...

C 구조체에는 몇 가지 제한이 있습니다. 근본적인 규칙은 구조체에 적용할 수 있는 유일한 연산은 주소를 취하기 위한 &와 멤버에 접근하는 것 밖에 없다는 것입니다. 따라서 구조체를 한 단위로 대입하거나 복사하는 것도 불가능하며, 함수에 전달하거나 함수 결과값으로 반환하는 것도 불가능합니다. (이런 제한은 이후 버전에서 완화될 예정입니다.) 하지만, 구조체를 가리키는 포인터에는 이런 문제가 없으므로 함수에 구조체를 전달하고 반환하는 데에는 현실적 문제는 없습니다.

이처럼 유사한 특성의 집합형임에도 구조체는 "값"의 개념이 존재하고 배열은 그렇지 않은 듯 하지만, 문제는 구조체가 배열을 멤버로 품게 되면서 발생합니다 - 사실, 1989년 표준이 작성될 당시 이런 가능성을 염두에 두지 못했다는 게 제 판단입니다.

어떤 문제인지는 다음 글에서 이어가겠습니다.