[OS]/Embedded&Linux

C99 이해를 위한 배경 지식과 새 기술 소개 - 2

하늘을닮은호수M 2007. 8. 6. 10:41
반응형

출처 : http://hawkshim.tistory.com/entry/펌-C99-이해를-위한-배경-지식과-새-기술-소개-2

저+자+소+개
전웅 | woong@icu.ac.kr http://www.woong.org

현재 한국정보통신대학교(ICU) 석사 과정에 재학 중이며, 컴파일러와 병렬처리 및 각종 표준에 깊은 관심을 가지고 있다
===============================================================================================================

C99의 새 기술들
이제부터 부족하게나마 다져둔 기반 지식을 바탕으로 C99의 새로운 기술들을 차례로 살펴보도록 하자. 사실 C90 표준과 C99 표준을 펼쳐놓고 한 줄씩 대조해가며 차이점을 찾아보면 상당히 방대한 양의 차이를 확인할 수 있다. 하지만 실제 프로그래밍 환경에 유효한 영향을 미치는 변화는 C99 표준의 머리말(foreword)에서 나열하고 있는 50여개로 정리될 수 있다. 그 중 일부 기술은 부동소수 연산과 IEEE 754 표준과 관련된 고급 기술에 대한 깊이 있는 이해를 요구하기에 이 연재에서도 겨우 소개만 할 계획이다. 기본적으로 제한된 지면에서 다룰 기술들은 크게 다음과 같은 기준에 따른다.


◆ 과거 표준(C90이나 C95)의 단점을 보완하기 위해 도입된 새 기술은 그 기술의 도입 배경을 중심으로 설명한다.
◆ C++에서 이미 지원하고 있는 기술은 C 언어에서의 몇 가지 특징과 주의사항만을 소개한다.
◆ 현재 다수의 컴파일러가 지원하고 있고, 프로그래밍 환경에서 매우 유용하게 쓰일 수 있는 기술은 비교적 구체적으로 설명한다.
◆ 가급적 각 기술의 구체적인 모습을 확인할 수 있는 예를 보인다.


이 글을 보면서 가장 유의해야 할 사항은 이 글이 C99의 새 기술에 대한 완벽한 설명을 제공하지는 않는다는 사실이다. 이 글의 주요한 목적은 C99가 대충 어떠한 모습을 가지고 있는지, C 언어가 어떠한 모습으로 변해가고 있는지 짚을 수 있는 큰 흐름만을 보이고자 하는 것이다. 지금부터 소개하는 기술의 제목 옆에 붙은 표시는 다음과 같은 의미로 쓰였다.


◆ Lang : C 표준은 크게 언어 부분과 라이브러리 부분으로 구분되어 있다. 실제 C 언어의 구현체인 컴파일러와 라이브러리는 서로 밀접하게 관련되어 칼로 자르듯이 나누기 어려운 경우(예를 들어 특정 라이브러리 함수 호출에 대해 컴파일러가 최적화된 코드를 삽입해 주거나 특정 라이브러리 함수가 컴파일러에서 제공되는 특정 기술을 사용해야만 하는 경우 등)가 많지만, 표준은 언어를 추상적인 관점에서 기술하고 있기에 언어와 라이브러리를 명확히 나누고 있다. 그 중 언어 부분에 추가된 변화일 경우 이와 같은 표시가 붙는다.
◆ Lib : C 표준의 라이브러리 부분에서 일어난 변화일 경우에 붙는다.
◆ C++ : C++를 통해 이미 유명해진 기술일 경우 붙는다. 다만 C와 C++에서 제공되는 기술이 정확히 동일한 의미를 갖는 것은 아님을 유의하기 바란다. 외양은 동일해도 세부적으로는 다소 다른 행동을 갖기도 한다.
◆ Open : 리눅스나 BSD 같은 오픈소스 환경에서 제공되는 컴파일러에서 직접 혹은 유사한 형태로 이미 제공되는 기술일 경우에 붙는다. 물론 시간이 지나 거의 모든 환경에서 C99가 완벽하게 지원된다면 이 표시는 무의미해질 것이다.


이중자와 를 통한 제한된 문자셋 지원 [Lang][Lib] [Open]
이 기술은 사실 C99가 아닌 AMD1에서 추가된 기술이다. AMD1는 C90에 붙는 부록 형태로 발표된 반면, C99가 사실상 C90의 첫 번째 개정이기에 AMD1에 추가된 주요한 기술이 C99에도 나열된 셈이다. 초기 C 언어가 기반으로 둔 문자셋은 우리에게 너무 익숙한 7비트 문자셋인 ASCII이다(공식적인 ASCII는 7비트 문자셋이다. 우리가 흔히 알고 있는 8비트 문자셋은 ASCII의 확장 버전으로 보통 ASCII-8이라고 부르며, 이 경우 코드 번호 128번 이후의 문자에 대한 이식성은 보장되지 않는다).
하지만 전 세계 모든 환경에서 ASCII의 128개 문자가 같은 의미, 같은 모양으로 쓰이지 않기에 국제적인 환경을 지향하는 C 표준은 ASCII를 언어의 기반 문자셋으로 도입할 수 없었다. 따라서 여러 나라에서 변형되어 사용되는 ASCII의 공통 부분(ASCII의 부분 집합)만을 묶은 ISO 646 Invariant Set을 언어의 기반 문자셋으로 삼았으며, 이로 인해 적절하게 표현할 수 없는 9개 문자를 ISO 646 Invariant Set 내의 문자들로 표현할 수 있도록 하기 위해 C90에서 특이한 형태의 삼중자(trigraph)를 도입하게 된다. <리스트 1>은 이런 삼중자를 사용한 프로그램을 보여주고 있다. 많은 개발 환경이 널리 쓰이지 않는 삼중자를 기본적으로 인식하지 않기에 별도의 옵션(gcc의 경우 -ansi -pedantic)을 주어야 올바르게 번역되지만, 이 이상한 모양의 프로그램이 분명 표준을 따르는 올바른 프로그램임에 유의하자.
하지만 이 삼중자는 모양 자체도 프로그램의 가독성(readability)을 심하게 떨어뜨릴 정도로 흉할 뿐더러 컴파일러가 프로그램을 인식하는 가장 초기 단계에 마치 워드프로세서의 바꾸기 기능처럼 무식하게 처리되기 때문에(심지어 문자열 상수 안에서의 삼중자도 치환된다) 정작 이를 필요로 하는 곳에서조차 외면받게 됐다. 이렇게 유럽 일부 국가를 중심으로 삼중자에 대한 불만이 나타나자 빈번하게 사용되는 삼중자 일부를 대체하기 위해 새로 추가된 것이 6개의 이중자(digraph)다. 이중자는 그 행동이 언어를 구성하는 다른 토큰(token)들과 같다는 장점을 갖고 <리스트 2>에서 볼 수 있듯이 외관도 상대적으로 수려하다.
또한 ASCII를 제대로 지원하지 못하는 환경에서도 가독성 높은 프로그램을 작성할 수 있도록 다음 예에서 볼 수 있듯이 삼중자나 이중자를 통해 기술되는 연산자들을 매크로(예를 들면 &&를 위한 and, ||를 위한 or, ~를 위한 compl)로 제공하는 가 추가로 지원된다.

%:include
// ...
if (isspace(uc) or isalpha(uc))
flag = compl flag;

다행스럽게도 우리나라의 대다수 기본 프로그래밍 환경은 ASCII의 128개 문자를 제대로 제공하고 있기에 삼중자나 이중자 혹은 와는 거리가 멀다. 다만 종종 삼중자나 이중자의 존재를 모르는 상태에서 그와 유사한 형태의 특수 문자를 프로그램 내에 사용했다가 이해할 수 없는 경고 메시지로 고민하는 경우가 있기에 그 존재만큼은 기억해 둘 필요가 있다.





유효 데이터형을 통해 자세해진 에일리어징 규칙 [Lang][Open]
C 언어에서 에일리어징(aliasing)이란 메모리에 존재하는 하나의 대상체(object)에 접근할 수 있는 경로가 다양함을 의미한다. 이는 보통 공용체와 포인터를 통해 일어난다. 예를 들어 다음과 같은 프로그램에서는 대상체에 접근할 수 있는 2가지 방법(object를 통한 방법, 이를 가리키는 포인터 pi를 통한 방법)이 존재한다.

#include
int main(void)
{
int object = 7903, *pi = &object;

printf(%d, %d , object, *pi);

return 0;
}

중요한 것은 C 언어가 모든 가능한 에일리어징을 허락해 주지는 않는다는 사실이다. 포인터의 정렬 제한(alignment restriction) 문제는 차치하고라도 표준이 에일리어징을 상당히 제한하는 큰 이유 중 하나는 바로 최적화와 관련되어 있다.
<리스트 3>에서 주석이 설명한대로 another_func()에 대한 호출을 최적화할 수 있을까? 만약 다음과 같은 함수 호출이 허용되는 것이라는 주석에서 보인 최적화는 허락되어서는 안 될 것이다.

int i;
func(&i, (float *)&i); // wrong

하지만 실질적으로 지원할 가치가 별로 없는 에일리어징을 위해 효율적인 최적화를 과도하게 막는 것은 언어의 성능 면에서 결코 바람직하지 않기에 표준은 float형 대상체를 int형으로 에일리어징하는 것을 허락하지 않는다. 결과적으로 주석에서 보인 최적화는 항상 허락된다. 즉 포인터의 정렬 제한 문제가 없다고 해도 앞에서 보인 것 같이 func()를 호출해 불법적인 에일리어징을 시도하는 프로그램은 최적화로 인해 전혀 엉뚱한 결과를 얻을 수도 있다. 물론 모든 에일리어징이 금지되어 마땅한 것은 아니다.
예를 들어 <리스트 4>와 같은 함수 func()가 주어졌을 때, 다음과 같은 무부호/유부호 정수형 사이의 에일리어징은 충분히 허락할 가치가 있다. 이는 표준이 무부호/유부호 정수형의 내부 표현의 형태를 어느 정도 보장하기에 에일리어징을 통해 의미 있는 결과를 얻을 수 있기 때문이다.

int i;
func(&i, (unsigned int *)&i); // okay

따라서 이 같은 경우에는 컴파일러가 함수 func() 안에서 pui와 pi가 가리키는 대상체가 다름을 확신하지 못하는 이상 another_func (*pui);을 another_func(2);으로 최적화할 수 없게 된다. 이렇게 C 언어는 일부 최적화를 제한하면서까지 허용해 줄 필요가 있는 에일리어징과 무의미하기에 금지되어야 마땅한 에일리어징을 구분하고 있지만 불행히도 C90에서는 그 규칙이 완벽하지 못했다. 예를 들어 malloc()를 통해 동적으로 할당되는 대상체의 경우, 선언된 대상체와는 달리 기본적으로 갖는 데이터형이 없기 때문에 무엇을 기준으로 에일리어징을 허가 혹은 금지해야 하는지 불분명했다. 따라서 C99에서는 이러한 경우까지도 모두 포용할 수 있도록 유효 데이터형(effective type)이라는 개념을 도입해 에일리어징 규칙을 상세히 기술하고 있다. 하지만 보완된 규칙 역시 여전히 공용체와 관련된 에일리어징을 올바르게 다루지 못해 현재 표준화 위원회는 이 부분에 대한 해결책을 마련 중에 있다(자세한 문제는 http://groups.google. com/groups?selm=G%25_i7.1992%24T4.16873%40www.newsranger.com 참고).





본문 출처 : 마소

반응형