> 공부를 하다보니 호환되는 데이터형인지 아닌지 구별해야할 경우가 있을꺼 같은데요
>
> C언어에서 호환되는 데이터형을 어떻게 구별할수 있을까요?
>
> 굉장히 쉬운 질문이긴 한데 웹서칭으로 도저히 찾을수가 없어서 T.T
>
type 을 올바르게 이해하지 못한 상황에선 쉬운 문제가 아닙니다.
일단, 서로 "같은" type 이면 호환되는 type 입니다. 여기에 추가로 서로
같지 않아도 충분히 비슷한 경우 추가로 호환되는 type 이 되는 경우가
있습니다 - 따라서 앞 문장의 역은 참이 아닙니다. 예를 들어, 크기를
명시하지 않은 배열과 명시된 배열은 요소 type 이 같으면 (서로 다른
type 이지만) 호환되는 type 입니다. 이런 추가적인 규칙은 책에서도 잘
다루고 있을 겁니다.
서로 다른데 호환되는 type 이 되는 경우가 많지 않기 때문에, 일단 완전히
같은 type 인지 살피고, 그렇지 않은 경우 서로 다른 type 이 호환될 수
있는 경우에 해당하는지 판단하면 간단히 호환 type 여부를 확인할 수
있습니다. 물론 type 에 대해 올바르게 이해하고 있을 때 가능한 이야기
입니다.
호환 type 을 100% 정확히 알려주는 툴은 따로 없습니다. 사람이 보는
type 이 컴파일러가 보는 type 보다 추상적이기 때문에, 반드시 사람의
올바른 판단이 우선하고 이를 보조하는 수단으로만 컴파일러가 사용되어야
합니다.
보조적 수단으로 컴파일러를 사용하고자 한다면, 상황에 따라 여러 방법이
있을 수 있겠지만 가장 일반적으로 사용할 수 있는 방법으로는 대입 연산자
가 있습니다.
대입 연산자에서 양쪽 피연산자가 포인터인 경우, "기본적으로" 두
포인터가 가리키는 type 이 호환되어야 합니다. 이와 같은 규칙은
"constraint" 로 주어져 있으며, constraint 는 프로그램이 지키지 못할
경우 표준을 따르는 컴파일러는 반드시 진단 메시지(경고나 오류 메시지)를
출력하도록 요구하고 있습니다.
따라서 A 라는 type 과 B 라는 type 이 호환되는지 확인하고자 한다면
pointer to pointer to A 와 pointer to pointer to B 를 만들어 하나를
다른 하나에 대입해 보며 컴파일 결과를 확인해 볼 수 있습니다. (왜
pointer to ... 가 아닌 pointer to pointer to ... 를 해야 하는지는 아래
에서 설명합니다.)
예를 들어, char 가 signed char 와 동일한 표현 범위(SCHAR_MIN ~
SCHAR_MAX)를 가질 때 char 가 type algebra 에서도 signed char 와 "호환"
될 수 있는 type 인지 확인하고자 한다면,
int main(void)
{
char **ppc;
signed char **pps;
(void) sizeof(ppc = pps); /* valid or invalid? */
}
과 같은 간단한 코드가 표준 모드(gcc 의 경우 -ansi -pedantic)에서 아무
진단 메시지 없이 번역되는지 확인해 볼 수 있습니다. (sizeof 를 쓴 이유
는 수식 p2 = p1 이 실제 수행되는 것을 막기 위한 것입니다.)
확인하고자 하는 type 의 포인터를 한번이 아닌 두번 취하는 이유는, 대입
연산자가 호환되지 않는 경우에도 대입을 허용하고 있는 경우를 가능한
피하기 위해서입니다. 예를 들면,
- void 와 int 는 당연히 호환되지 않는 type 입니다. 만약, 위에서
말씀드린대로 포인터를 2번 취하지 않고 한번만 취해 대입 연산자로
시험해보면 어떤 결과가 발생할까요?
void *pv;
int *pi;
(void) sizeof(pv = pi); /* no problem */
(void) sizeof(pi = pv); /* no problem */
void * 가 대상체 포인터의 일반 포인터(generic pointer)이기에 대상체
포인터 int * 를 대입하거나 그 반대로 int * 를 void * 에 대입하는 것이
특별히 허용되고 맙니다. 이런 경우를 위해 위에서 말씀드린대로 해당 type
(void 와 int) 의 pointer 를 두 번 취해 대입할 경우 아래와 같이 기대한
결과를 확인할 수 있습니다.
- const int 와 int 는 호환되지 않는 type 입니다. 이 경우에도 포인터를
한번만 취하게 되면 pointer to const int 로 pointer to int 를 대입하는
것이 가능해집니다 (좌측 피연산자로 주어진 포인터가 가리키는 type 이
우측 피연산자로 주어진 포인터가 가리키는 type 보다 "더" 한정되어 있는
경우에는 호환되지 않아도 대입이 허용됩니다).
const int *pci;
int *pi;
(void) sizeof(pci = pi); /* no problem */
하지만, 이 경우에도 포인터를 두번 취하면 호환되지 않는 type 임을
확인할 수 있게 됩니다.
const int **ppci;
int **ppi;
(void) sizeof(ppci = ppi); /* invalid */
마지막으로, C 언어에서 각 열거 type 은 어떤 정수형과 호환되는 type
입니다. 하지만, (몇가지 조건이 만족되기만 하면) 그 호환되는 정수형으로
무엇이 선택될지는 알 수 없기 때문에 사실상 열거 type 과 정수형은 서로
다른 type 으로 가정해야 안전합니다. 하지만, 만약 특정 구현체에서 어떤
열거형과 int 형이 호환되는 type 으로 선택되었다면, 포인터를 두번
취해서 대입을 하더라도 진단이 발생하지 않습니다. 이는 "해당"
구현체에서 두 type 이 실제로 호환이 되기 때문입니다만, 그렇다고 "모든"
구현체에서 둘을 호환되는 type 으로 가정할 수 있다고 의미하는 것은
아닙니다.
이 정도면 주의해야 할 경우는 다 언급한 것 같습니다. 하지만, 이런
실험적인 방법은 어디까지나 호환 type 이해의 보조 수단임을 다시 한번
강조합니다.
> 공부를 하다보니
> 공부를 하다보니 호환되는 데이터형인지 아닌지 구별해야할 경우가 있을꺼 같은데요
>
> C언어에서 호환되는 데이터형을 어떻게 구별할수 있을까요?
>
> 굉장히 쉬운 질문이긴 한데 웹서칭으로 도저히 찾을수가 없어서 T.T
>
type 을 올바르게 이해하지 못한 상황에선 쉬운 문제가 아닙니다.
일단, 서로 "같은" type 이면 호환되는 type 입니다. 여기에 추가로 서로
같지 않아도 충분히 비슷한 경우 추가로 호환되는 type 이 되는 경우가
있습니다 - 따라서 앞 문장의 역은 참이 아닙니다. 예를 들어, 크기를
명시하지 않은 배열과 명시된 배열은 요소 type 이 같으면 (서로 다른
type 이지만) 호환되는 type 입니다. 이런 추가적인 규칙은 책에서도 잘
다루고 있을 겁니다.
서로 다른데 호환되는 type 이 되는 경우가 많지 않기 때문에, 일단 완전히
같은 type 인지 살피고, 그렇지 않은 경우 서로 다른 type 이 호환될 수
있는 경우에 해당하는지 판단하면 간단히 호환 type 여부를 확인할 수
있습니다. 물론 type 에 대해 올바르게 이해하고 있을 때 가능한 이야기
입니다.
호환 type 을 100% 정확히 알려주는 툴은 따로 없습니다. 사람이 보는
type 이 컴파일러가 보는 type 보다 추상적이기 때문에, 반드시 사람의
올바른 판단이 우선하고 이를 보조하는 수단으로만 컴파일러가 사용되어야
합니다.
보조적 수단으로 컴파일러를 사용하고자 한다면, 상황에 따라 여러 방법이
있을 수 있겠지만 가장 일반적으로 사용할 수 있는 방법으로는 대입 연산자
가 있습니다.
대입 연산자에서 양쪽 피연산자가 포인터인 경우, "기본적으로" 두
포인터가 가리키는 type 이 호환되어야 합니다. 이와 같은 규칙은
"constraint" 로 주어져 있으며, constraint 는 프로그램이 지키지 못할
경우 표준을 따르는 컴파일러는 반드시 진단 메시지(경고나 오류 메시지)를
출력하도록 요구하고 있습니다.
따라서 A 라는 type 과 B 라는 type 이 호환되는지 확인하고자 한다면
pointer to pointer to A 와 pointer to pointer to B 를 만들어 하나를
다른 하나에 대입해 보며 컴파일 결과를 확인해 볼 수 있습니다. (왜
pointer to ... 가 아닌 pointer to pointer to ... 를 해야 하는지는 아래
에서 설명합니다.)
예를 들어, char 가 signed char 와 동일한 표현 범위(SCHAR_MIN ~
SCHAR_MAX)를 가질 때 char 가 type algebra 에서도 signed char 와 "호환"
될 수 있는 type 인지 확인하고자 한다면,
과 같은 간단한 코드가 표준 모드(gcc 의 경우 -ansi -pedantic)에서 아무
진단 메시지 없이 번역되는지 확인해 볼 수 있습니다. (sizeof 를 쓴 이유
는 수식 p2 = p1 이 실제 수행되는 것을 막기 위한 것입니다.)
확인하고자 하는 type 의 포인터를 한번이 아닌 두번 취하는 이유는, 대입
연산자가 호환되지 않는 경우에도 대입을 허용하고 있는 경우를 가능한
피하기 위해서입니다. 예를 들면,
- void 와 int 는 당연히 호환되지 않는 type 입니다. 만약, 위에서
말씀드린대로 포인터를 2번 취하지 않고 한번만 취해 대입 연산자로
시험해보면 어떤 결과가 발생할까요?
void * 가 대상체 포인터의 일반 포인터(generic pointer)이기에 대상체
포인터 int * 를 대입하거나 그 반대로 int * 를 void * 에 대입하는 것이
특별히 허용되고 맙니다. 이런 경우를 위해 위에서 말씀드린대로 해당 type
(void 와 int) 의 pointer 를 두 번 취해 대입할 경우 아래와 같이 기대한
결과를 확인할 수 있습니다.
즉, 위에서 설명드린대로 void 와 int 는 당연히 호환되지 않는 type
입니다.
- const int 와 int 는 호환되지 않는 type 입니다. 이 경우에도 포인터를
한번만 취하게 되면 pointer to const int 로 pointer to int 를 대입하는
것이 가능해집니다 (좌측 피연산자로 주어진 포인터가 가리키는 type 이
우측 피연산자로 주어진 포인터가 가리키는 type 보다 "더" 한정되어 있는
경우에는 호환되지 않아도 대입이 허용됩니다).
하지만, 이 경우에도 포인터를 두번 취하면 호환되지 않는 type 임을
확인할 수 있게 됩니다.
마지막으로, C 언어에서 각 열거 type 은 어떤 정수형과 호환되는 type
입니다. 하지만, (몇가지 조건이 만족되기만 하면) 그 호환되는 정수형으로
무엇이 선택될지는 알 수 없기 때문에 사실상 열거 type 과 정수형은 서로
다른 type 으로 가정해야 안전합니다. 하지만, 만약 특정 구현체에서 어떤
열거형과 int 형이 호환되는 type 으로 선택되었다면, 포인터를 두번
취해서 대입을 하더라도 진단이 발생하지 않습니다. 이는 "해당"
구현체에서 두 type 이 실제로 호환이 되기 때문입니다만, 그렇다고 "모든"
구현체에서 둘을 호환되는 type 으로 가정할 수 있다고 의미하는 것은
아닙니다.
이 정도면 주의해야 할 경우는 다 언급한 것 같습니다. 하지만, 이런
실험적인 방법은 어디까지나 호환 type 이해의 보조 수단임을 다시 한번
강조합니다.
--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org (서버 공사중)
--
Jun, Woong (woong at gmail.com)
http://www.woong.org
댓글 달기