함수 포인터에 대한 질문......

doodoo의 이미지

지금 커닝헴 저서 C programming Langguage 를 보고 있는데
sort 함수를 호출하는 부분을 읽다가 도저히 감을 잡을수가 없군요

#include <stdio.h>
#include <string.h>
 
#define MAXLINES 5000
 
char *lineptr[MAXLINES];
 
int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);
 
void qsort(void *lineptr[], int left, int right, int (*comp)(void*, void *));
int numcmp(char *, char *);
 
 
main (int argc, char *argv[])
{
        int nlines;
        int numeric = 0;
 
        if(argc > 1 && strcmp(argv[1], "-n") == 0)
                numeric =1;
        if((nlines = readlines(lineptr, MAXLINES)) >= 0) {
                qsort((void **) lineptr, 0, nlines -1, (int (*)(void*,void*))(numeric ? numcmp : strcmp));
                writelines ( lineptr, nlines);
                return 0;
        } else {
                printf("input too big to sort\n");
                return 1;
        }
}
 
 
void qsort(void *v[], int left, int right, int(*comp)(void *, void *))
{
        int i, last;
        void swap(void *v[], int, int);
 
 
        if(left >= right)
                return;
        swap(v,left,(left + right)/2);
        last = left;
        for (i = left +1; i<=right; i++)
                if((*comp)(v[i], v[left]) < 0)
                        swap(v, ++last, i);
        swap(v, left, last);
        qsort(v, left, last-1, comp);
        qsort(v, last+1, right, comp);
}

윗 부분에 보면 원형이
void qsort(void *lineptr[], int left, int right, int (*comp)(void*, void *));
이렇게 되어 있지요...
그런데...호출시에는...
qsort((void **) lineptr, 0, nlines -1, (int (*)(void*,void*))(numeric ? numcmp : strcmp));
이런 식이구요

어째서 numeric ? : 부분의 결과값이 int (*) 의 안으로 대치될수 있는 건가요?
위에서 생각한것은 numeric 가 1일 경우
int (*numcmp)(void*, void *) 가 되도록 의도 한거 같은데....
실제로는 int (*)(void*,void*))(numcmp) 이렇게 되는것 아닌가요?
아니면 결과쪽으로 동일하다는 건가?...아..햇갈려요..

hey의 이미지

numeric이 함수 포인터인 것 같군요. 커스텀 함수포인터가 있으면 그걸 사용하고, 없으면 strcmp를 사용하도록 되어 있는 것 같네요. 앞의

(int (*)(void*,void*))

이것은 캐스팅이고요.

May the F/OSS be with you..



----------------------------
May the F/OSS be with you..


moonhyunjin의 이미지

numeric이 참이면 numcmp 함수의 주소가 넘어가고요. 아니면 strcmp 함수의 주소가 넘어가요.

<- 이거면 안되는 게 없어~
정품 소프트웨어 사용 캠패인

<- 이거면 안 되는 게 없어~
정품 소프트웨어 사용 캠패인

doodoo의 이미지

네...그러니깐...

위에서 생각한것은 numeric 가 1일 경우
int (*numcmp)(void*, void *) 가 되도록 의도 한거 같은데....
실제로는 int (*)(void*,void*))(numcmp) 이렇게 되는것 아닌가요?

처럼 numeric 의 값에 따라 주소가 넘어가게 했다는 건 알고 있지만
실제로 (int (*)(void*,void*))(numeric ? numcmp : strcmp)); 이 구문 자체는
int (*)(void*,void*))(numcmp) 이렇게 되는것 아닌가 해서요...

왜 이것이 int (*numcmp)(void*,void*) 가 되는 거지요?

draca의 이미지

(int (*)(void*,void*))(numeric ? numcmp : strcmp))

(int (*)(void*,void*)) 이 부분은 type casting 입니다.
(numeric ? numcmp : strcmp) 이 부분에서 실제 원하는 function의 주소값을 얻어오게 되구요.
numcmp 라는 주소값으로 (int (*)(void*, void*)) 형으로 함수를 호출하게 되는 것이지요.

그럼 (int (*numcmp)(void*,void*)) 와 같은 것입니다.

결국 (int(*)(void*,void*))(numcmp)) 과 (int (*numcmp)(void*,void*)) 는 동일합니다.

함수를 이와 같은 형으로 선언할 수 있다는 것을 이해하시면 될 것 같네요.

doodoo의 이미지

그러면 위의 내용을 축약하여 보면

(int (*)())(aa) 와
int (*aa)() 는 같다는 예기가 되지요?

얼핏 눈으로 봐도 두개는 같지 않아 보이는데요...아 모르겠다.

추가: 갑자기 타입케스팅이란 낱말로 유심히 보니깐 같아 보이네요. 감사합니다.

lovewar의 이미지

int test(void)
{
    return 10;
}
int main(void)
{
    int (*funp)(void);    /* 번수 선언입니다. */
 
    funp = &test;   /* 함수의 번지를 지칭하는 &를 사용하여 명확성을 확보하는 것이 좋습니다. */
 
   printf("%d \n", funp());
 
   return  0;
}

전웅의 이미지

지나가다가 남깁니다...

int numcmp(char *, char *);
int strcmp(const char *, const char *);
 
qsort(..., (int (*)(void*,void*))(numeric ? numcmp : strcmp));

이 코드는 잘못된 코드입니다.

모든 함수 포인터는 동일한 표현과 동일한 정렬 제한을 갖는다고 보장되기
때문에 정보 손실 없이 서로 다른 함수 포인터형으로 변환될 수 있습니다.
하지만, 그렇게 변환된 함수 포인터를 통해 호환되지 않는 type 으로
원래의 함수를 호출할 수 있는 것은 아닙니다. 즉,

int func(float f);

라는 함수를

void (*fp)(double);

로 선언된 함수 포인터에 강제 대입한 후 fp 를 통해 함수 func() 를
호출하는 것은 허용되지 않는다는 뜻입니다. 유사하게 qsort() 가 요구하는
callback 함수의 type 과 numcmp(), strcmp() 의 type 이 호환되지 않기
때문에 이를 강제로 형변환해 qsort() 에 callback 함수로 제공하는 것은
잘못된 프로그램입니다.

물론, char * 와 void * 가 같은 표현, 같은 정렬제한을 갖는다는 사실이
보장되기 때문에 적지 않은 환경에서 운 좋게도 원하는 결과를 얻을 수
있겠지만, 표준 차원의 이식성은 결코 보장될 수 없습니다. 따라서
strcmp() 를 qsort() 의 callback 함수로 제공하고자 한다면, 매개변수
type 을 조정해 줄 수 있는 wrapping 함수를 도입해야 합니다.

"표준 C(ANSI C)"를 표방하는 K&R2 이지만, 사실상 최종 표준 발표 이전에
쓰여진 책이라 정확하게 기술되지 못한 부분이 있는것이 아닐까 싶습니다.

그럼...

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

지난번에 전웅님께서, 모든 구조체의 정렬 제한은 같다고 말씀해주셨습니다. 그런 측면에서 다음과 같은 캐스트는 표준상으로 문제가 없는 것이 맞는지 확인해주셨으면 합니다.

int func1( struct _s1* s1 );
int (* func2)( struct _s2* s2 ) = (int (*)(struct _s2*))func1;

지난번에 이 문제때문에, 기 작성된 코드를 꽤 많이 수정했는데, 위와 같은 캐스팅은 남아있습니다.

---------------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

char * 와 void * 가 서로 호환되지 않는 type 이듯이, struct s1 * 과
struct s2 * 역시 (동일한 멤버로 구성되어 있다고 해도) 서로 호환되지
않는 type 입니다. 결국, int mycmp(char *, char *) 와 int mycmp(void *,
void *) 가 서로 호환되지 않는 type 인 것처럼, int mycmp(struct s1 *,
struct s1 *) 역시 int mycmp(struct s2 *, struct s2 *) 와 호환되지
않습니다.

그리고 일반적인 규칙에 의해 원래 정의된 type 과 호환되지 않는 type 을
통해 함수를 호출할 경우 그 행동은 정의되지 않습니다.

따라서 OP 의 문제에서와 마찬가지로 보여주신 강제 변환에 의한 호출
(변환 자체가 문제가 되는 것은 아닙니다만 나중에 호출하지 않을 것이라면
변환할 이유도 없겠지요) 역시 표준에 따르면 잘못된 프로그램입니다.

하지만, char * 와 void * 가 표준 요구에 의해 같은 표현, 같은 정렬제한
을 갖듯이, struct s1 * 과 struct s2 * 역시 표준 요구에 의해 (C90 에는
그와 같은 요구가 없었으나 opaque type 구현을 위해 사실상 필요한 사실임
이 밝혀져 C99 에 추가되었습니다) 같은 표현, 같은 정렬제한을 갖습니다.

따라서 보여주신 코드

int func1( struct _s1* s1 );
int (* func2)( struct _s2* s2 ) = (int (*)(struct _s2*))func1;

는 OP 의 문제와 동일한 위치에 있습니다. OP 의 코드가 현실적으로
작동하지 않을 가능성이 거의 없고 그 정도 이식성 손상은 감수할만하다고
판단하신다면, 보여주신 코드 역시 마찬가지입니다.

하지만, 그렇게 서로 호환되지 않는 구조체형을 인자로 받는 함수형 사이에
변환이 필수적이라는 사실은 두 구조체형이 사실상 동일한 구조체로
묶였어야 하거나 최소한 다음과 같이 공용체로 묶여야 했음을 의미합니다.

union foo {
    struct s1 { ... } common;    /* common initial sequence? */
    struct s1 { ... } s1obj;
    struct s2 { ... } s2obj;
};
 
int func1(union foo *);    /* s1obj 에 접근해 사용 */
int (*func2)(union foo *) = func1;    /* s2obj 에 접근해 사용 */

개발 과정에서 저도 자주 겪는 일이지만, 뻔히 알면서도 표준을 위반해야
하는 경우가 발생한다면 이는 십중팔구 초기 설계부터 잘못되어 왔다는
것을 의미하더군요 - 결국 전면 수정을 포기하고 타협해야 하는 경우가 될
가능성이 큽니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

하지만, 그렇게 서로 호환되지 않는 구조체형을 인자로 받는 함수형 사이에
변환이 필수적이라는 사실은 두 구조체형이 사실상 동일한 구조체로
묶였어야 하거나 최소한 다음과 같이 공용체로 묶여야 했음을 의미합니다.

저런 캐스팅을 할 때에는 위와 같은 내용은 당연한 기본 전제겠지요.
함수 포인터 캐스팅 문제에 집중했으면 좋겠구요.

이것 조차 표준 위배라면, 캐스팅을 아예 쓰지 않아야 된다는 말씀 같은데요.
그렇다면, C++ 에서 부모 클래스의 메소드를 자식 클래스 인스턴스가 호출하는 것은
C 표준상으로 어떤 상태라고 보시는지요?

------------------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

lovewar의 이미지

int numcmp(char *, char *);
int strcmp(const char *, const char *);
 
int main(void)
{
    int (* compatiblePtr) (void*, void*);
 
    compatiblePtr = (int (*)(void*,void*)) &strcmp;            /* int (*)(const char *, const char *)  to  int (*)(void *, void *)   */ 
 
    compatiblePtr = (int (*)(void*,void*)) &numcmp;        /* int (*)(char *, char *)  to  int (*)(void *, void *)   */ 
 
    return 0;
}

함수 포인터를 사용(호출)하는 시점에서 인자간 타입들이 호환 가능한지에 따라 달라질것 같은데요.
함수 포인터를 캐스팅하는 순간에 다른문제(정렬문제?)가 대두 될 수 있는 건가요?

전웅의 이미지

맨 아래 제 글에서 링크한 논의에서 충분히 다루었던 사항 이라고 생각
합니다. 요지는 매개변수의 type 이 type system 관점에서 "호환"되지
않으면, 아무리 그 type 의 표현, 정렬제한에 대한 추가적인 보장이
따라온다고 해도 두 함수형은 호환되지 않는다는 것입니다. 또한, (비원형
정의 함수를 비원형 선언 아래에서 호출할 때 정수형 및 void */char * 에
대한 예외적인 경우를 제외하고) 함수를 정의된 type 과 다른 type 으로
호출하는 것은 이유를 막론하고 정의되지 않은 행동에 해당합니다 - 표준
을 보시면 대충 "정의된 함수의 type 이 그 함수를 호출하는 수식의 type
과 호환되지 않으면 행동이 정의되지 않는다" 라는 식의 문장이 있을
겁니다.

(void * 와 char * 는 호환되는 type 이 아닙니다. 또한, 모든 함수
포인터는 동일한 정렬제한, 표현을 갖지만 그렇다고 호환되지 않는 type
으로 변환한 후에 호출하는데 아무 문제가 없다는 것은 아닙니다.)

물론, K&R2 에 나온 예와 같은 코드가 실제 환경에서 문제를 얼마나 일으킬
수 있느냐는 또 다른 문제가 됩니다. 이에 대한 이야기 역시 링크했던 논의
에서 다루어졌습니다.

늘 드리는 말씀이지만, 프로그램 내에 포인터형 사이의 강제 변환이
나타났다면 (손가락으로 꼽을 수 있는 예외를 제외하면) 뭔가를 잘못 하고
있을 가능성이 크다는 것을 의미합니다.

p.s. 쓰레드 구조가 보이는 것은 좋지만, 제가 글을 남긴 이후 어느 분이
그 글에 대해 답글을 남기셨는지 한눈에 찾기가 쉽지 않네요.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

아래 글은 포인터 관련 변환들에 대한 표준문서의 일부인데요,

Quote:

A pointer to void may be converted to or from a pointer to any incomplete or object
type. A pointer to any incomplete or object type may be converted to a pointer to void
and back again; the result shall compare equal to the original pointer.

위 내용을 적용한다면 가능할것 같은데요.
모든 시스템에 적용가능하다고는 할수 없겠지만요.

여담으로, 포인터에서 호환 가능하다는 말은 사용하기가 애매할거라 생각합니다.
특정 하드웨어는 다르게 주소계산을 수행할 수도 있을테니깐요.
그래서 표준에서도 아마 may be를 사용하고 있지 않나 생각합니다.

전웅의 이미지

표준에서 may 는 "가능성"이 아니라 "허락"을 의미합니다. 따라서 void *
로의 변환이 모든 시스템에 적용되지 않을 "수도" 있다는 것이 아니라,
void * 로의 변환이 (표준에 의해) "허락"됨을 보장한다는 뜻입니다.

포인터 표현의 호환과 포인터 type 의 호환은 전혀 다른 문제입니다.
포인터 type 은 해당 포인터가 가리키는 type 이 호환되는 type 일 때 호환
된다고 정의됩니다. 즉, pointer to char 는 pointer to void 와 호환되지
않습니다. 다만, 특수한 목적을 위해 그 내부 표현과 정렬제한을 같도록
만들어 준 것뿐입니다. 참고로 pointer to char 는 pointer to signed char
혹은 pointer to unsigned char 와도 호환되지 않습니다. 이것이 아래와
같은 코드가 경고를 내는 이유입니다.

    signed char *psc = "string";

호환의 문제는 type algebra 의 문제이지 해당 type 표현이나 정렬제한의
호환 여부의 문제가 아닙니다.

또한, 인용하신 부분은 다음과 같은 구조를 허락해주기 위함입니다.

    int *pi = &i;
    int *pi2 = (void *) pi;
    pi == pi2;    /* pi, pi2 사이에 포인터로서의 정보 손실 없음 */

현재 문제가 되고 있는 함수 포인터 변환과 호출의 문제를 다루는 부분이
아닙니다. 현재 문제에 해당하는 표준의 부분은 아래와 같습니다.

Quote:

If the function is defined with a type that is not compatible
with the type (of the expression) pointed to by the expression
that denotes the called function, the behavior is undefined.

C FAQs 에서도 나오지만 K&R2 에 나온 부분이 표준 관점에서 "잘못된" 구조
라는 사실에는 변함이 없습니다. 표준에서 명백히 잘못되었다고 규정하고
있으므로 논의의 여지가 없습니다. 표준에서 그와 같은 구조가 정의되는 것
처럼 설명하는 부분을 찾으셨다면, 잘못 찾으셨거나 잘못 해석하신
것입니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

Quote:

맨 아래 제 글에서 링크한 논의에서 충분히 다루었던 사항 이라고 생각
합니다. 요지는 매개변수의 type 이 type system 관점에서 "호환"되지
않으면, 아무리 그 type 의 표현, 정렬제한에 대한 추가적인 보장이
따라온다고 해도 두 함수형은 호환되지 않는다는 것입니다. 또한, (비원형
정의 함수를 비원형 선언 아래에서 호출할 때 정수형 및 void */char * 에
대한 예외적인 경우를 제외하고) 함수를 정의된 type 과 다른 type 으로
호출하는 것은 이유를 막론하고 정의되지 않은 행동에 해당합니다 - 표준
을 보시면 대충 "정의된 함수의 type 이 그 함수를 호출하는 수식의 type
과 호환되지 않으면 행동이 정의되지 않는다" 라는 식의 문장이 있을
겁니다.

(void * 와 char * 는 호환되는 type 이 아닙니다. 또한, 모든 함수
포인터는 동일한 정렬제한, 표현을 갖지만 그렇다고 호환되지 않는 type
으로 변환한 후에 호출하는데 아무 문제가 없다는 것은 아닙니다.)

"(비원형 정의 함수를 비원형 선언 아래에서 호출할 때 정수형 및 void */char * 에
대한 예외적인 경우를 제외하고)"
이 내용(void */ char *)은 비원형 정의함수와 원형 정의함수에서 동일하게 행해져야 한다고 생각합니다.
왜냐하면, 인자(argument) 변환을 하더라도 정의된 함수에서 사용하는 파라미터의 자료형(void * / char *)이 다를 수 있게 때문입니다.

즉, "정의된 함수의 type 이 그 함수를 호출하는 수식의 type 과 호환되지 않으면 행동이 정의되지 않는다"
의 예외적인 조건이 될 수 있지 않나요?

동일하게 행해져야 한다는 것은 "정의되지 않는 행위를 하지 않는다"는 뜻이지 올바르게 행동을 보장한다는 것은 아닙니다.

전웅의 이미지

> > 맨 아래 제 글에서 링크한 논의에서 충분히 다루었던 사항 이라고 생각
> > 합니다. 요지는 매개변수의 type 이 type system 관점에서 "호환"되지
> > 않으면, 아무리 그 type 의 표현, 정렬제한에 대한 추가적인 보장이
> > 따라온다고 해도 두 함수형은 호환되지 않는다는 것입니다. 또한, (비원형
> > 정의 함수를 비원형 선언 아래에서 호출할 때 정수형 및 void */char * 에
> > 대한 예외적인 경우를 제외하고) 함수를 정의된 type 과 다른 type 으로
> > 호출하는 것은 이유를 막론하고 정의되지 않은 행동에 해당합니다 - 표준
> > 을 보시면 대충 "정의된 함수의 type 이 그 함수를 호출하는 수식의 type
> > 과 호환되지 않으면 행동이 정의되지 않는다" 라는 식의 문장이 있을
> > 겁니다.
> >
> > (void * 와 char * 는 호환되는 type 이 아닙니다. 또한, 모든 함수
> > 포인터는 동일한 정렬제한, 표현을 갖지만 그렇다고 호환되지 않는 type
> > 으로 변환한 후에 호출하는데 아무 문제가 없다는 것은 아닙니다.)
> >
>
> "(비원형 정의 함수를 비원형 선언 아래에서 호출할 때 정수형 및 void */char * 에
> 대한 예외적인 경우를 제외하고)"
> 이 내용(void */ char *)은 비원형 정의함수와 원형 정의함수에서 동일하게 행해져야 한다고 생각합니다.

이 말씀은 void */char * 에 대한 예외 처리가 함수 정의 형태의 원형/
비원형과는 무관하게 주어져야 한다는 뜻이죠?

이는 제가 2001년 4월에 csc 에 포스팅했던 주장이며, Clive Feather 에
의해 DR255 로 제출되었던 내용입니다. 이에 대한 위원회의 답변은 아래와
같습니다.

The Committee does not wish to further refine the behavior of calls
not in the scope of prototypes. In practice, this will not be a
problem, and the Committee does not wish to define the behavior.

즉, 함수 비원형과 관련된 행동은 비원형 아래에서의 함수 호출이 표준에
의해 권장되지 않는 사항인 만큼(표준 위원회에서는 비원형 방식을 C90
시절부터 구실 기술로 규정하고 있죠) 더 이상의 refinement 는 원치
않는다고 합니다.

사실 이 문제는 csc 에 포스팅했던 제 글에서도 나오지만, char */void *
보다 정수형과 관련해 더 심각한 문제를 낳습니다.

    -- a.c --
    void func(a)
    unsigned int a;
    {
        return;
    }
 
    -- b.c --
    void func();
    func(0);     /* okay */
    func(0u);    /* okay */

이제 func() 의 정의를 원형으로 바꾸면,

    -- a.c --
    void func(unsigned int a)
    {
        return;
    }
 
    -- b.c --
    void func();
    func(0);     /* UB */
    func(0u);    /* okay */

이는 누가 보기에도 비직관적이며, 그래서 DR 을 제출했었습니다. 하지만
위원회의 대답은 상당히 냉소적이죠 :-(

> 왜냐하면, 인자(argument) 변환을 하더라도 정의된 함수에서 사용하는 파라미터의 자료형(void * / char *)이 다를 수 있게 때문입니다.
>
> 즉, "정의된 함수의 type 이 그 함수를 호출하는 수식의 type 과 호환되지 않으면 행동이 정의되지 않는다"
> 의 예외적인 조건이 될 수 있지 않나요?
>
> 동일하게 행해져야 한다는 것은 "정의되지 않는 행위를 하지 않는다"는 뜻이지 올바르게 행동을 보장한다는 것은 아닙니다.
>

설사 제가 제안한 내용이 받아들여져 원형 정의에서도 void */char * 가
예외 처리 되었다고 해도 qsort() 와 관련된 문제와는 거리가 있습니다.
qsort() 함수의 매개변수는 원형으로 선언됩니다. 따라서 qsort() 함수
내에서의 callback 함수 호출은 항상 원형 선언 아래에서 이루어지게
됩니다. 그러면 이제 문제는 원형 선언과 비원형/원형 정의의 호환 문제로
바뀌게 되며, 이 문제는 비원형 아래에서의 호출과는 달리 function call
expression 부분이 아닌 function declarator 부분에서 정의됩니다. 따라서
원형 선언과 비원형/원형 정의의 호환 문제에서 void */char * 를 예외로
두는 것은 type compatibility 규칙에 일관성을 깨는 결과를 가져옵니다.

사실 void */char * 에 대한 예외는 C99 이전에는 없던 것입니다. C99 에서
(제 기억이 맞다면, 역시나 Clive Feather 가 제안한 바에 따라) void */
char * 예외가 추가되었지만, 아직도 일관성 없는 부분이 많습니다. 대표적
인 다른 한 경우가 가변 인자 처리입니다.

stdarg.h 를 사용해 가변 인자 처리 함수를 생성한 경우 해당 함수의
가변 인자 부분으로 char * 를 전달하고 함수 안에서 void * 로 취하는
것을 C99 에 와서 허락해 주었습니다. 하지만, 표준을 액면 그대로 해석할
경우 여전히 printf() 계열 함수의 "%p" 로는 char * 를 전달해주지
못하도록 되어 있습니다 - printf() 계열 함수가 표준의 stdarg.h 를
이용해 구현되도록 요구한 부분이 없기 때문입니다. 하지만, 가변 인자
처리에 void */char * 예외가 추가되었다는 사실 때문에 적지 않은 사람들
이 printf() 의 "%p" 에도 char * 를 전달하는데 아무 문제가 없다고 오해
하기 쉽습니다. 아마도 이 부분에 대해서도 진정한 의도가 무엇인지 (즉,
printf 에서도 허락하는 것이 의도였는데 제대로 반영을 하지 못한 것인지
아니면 정말로 이상하게도 printf 에 있어서만큼은 허락하지 않으려 한
것인지) 확인하기 위한 DR 이 필요한 상황입니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

Quote:

설사 제가 제안한 내용이 받아들여져 원형 정의에서도 void */char * 가
예외 처리 되었다고 해도 qsort() 와 관련된 문제와는 거리가 있습니다.
qsort() 함수의 매개변수는 원형으로 선언됩니다. 따라서 qsort() 함수
내에서의 callback 함수 호출은 항상 원형 선언 아래에서 이루어지게
됩니다. 그러면 이제 문제는 원형 선언과 비원형/원형 정의의 호환 문제로
바뀌게 되며, 이 문제는 비원형 아래에서의 호출과는 달리 function call
expression 부분이 아닌 function declarator 부분에서 정의됩니다. 따라서
원형 선언과 비원형/원형 정의의 호환 문제에서 void */char * 를 예외로
두는 것은 type compatibility 규칙에 일관성을 깨는 결과를 가져옵니다.

우선 함수 호출 표현식에서 인자들이 변환하는 방식에는 두가지가 존재한다는 것을 이해하고 있어야 합니다(모르는 분들에게 있어).
//단순화 시킨것이니 자세한 것은 확인하시기 바랍니다.
. 비-원형 함수 선언시 인자들의 변환방법
// default-argument promotion을 따릅니다.
// 예외적인 사항 두가지(전웅님 글 참조)
. 원형 함수 선언시 인자들의 변환 방법
// 대응되는 파라미터로 변환을 수행합니다.
. 그외 변환되지 않습니다.
여기서, 중요한 것은 함수 선언(정의가 아닌)으로 인자들을 파라미터 자료형으로 변환한다는 것입니다.

이 인자들의 변환이 정상적으로 수행되었다면, type compatibility 규칙에 문제가 발생하지 않습니다.
하지만, 선언에 대한 변환이기 때문에 정의와의 호환 문제가 남아 있습니다.
// 개인적으로 언어의 단점이자 장점으로 생각합니다(실수에 의해 선언과 정의를 다르게 할수 있기에).

그런 예제(변환되지 않는 경우)는 두가지 형태를 띌수 있습니다.
하나는 전웅님이 설명하신 함수 포인터에 대한 호출시 상황이고, 다른 하나는 아래 같은 경우입니다.

extern int foo(int, char, double);   /* 주석 1 :   함수원형을 선언합니다.  */
 
void test(void)
{
 
   (void) foo(0x10, 'c', 3.14D);    /* 주석 2:  인자들을 주석 1의 파라미터로 형 변환합니다.  */
 
}
 
extern int foo(double f64a, double f64b, double f64c)   /* 주석 3 : 실행시 파라미터들을 함수 내부에서 접근할(UB) 것입니다. */
{
     /* do something */
}

그런 void * / char * 간에 비-원형 함수선언시 어떻게 변환이 되는지를 생각해야 합니다.

생각을 해 보면 변환 자체를 할 수 없습니다.
// 비-원형 함수나 원형 함수나 동일합니다.

그리고, type compatibility 규칙에 일관성을 깨는 결과를 가져옵니다.
이것은 전체적으로는 맞는 논리 입니다. 하지만 이 함수 호출 규칙(?)들에서
변환하지 못한것에 한정해야 하지 않을까 생각합니다(이부분에 대해서 듣고 싶습니다).

type compatibility 규칙에 깨는 것이라면 wrapper용 함수를 사용한 것은 모순에 해당되는 것이라 생각합니다.
// void * / char * 간의 변환은 보장 받을 수 없다는 논지가 되버리기 때문입니다.

그리고, 이는 다른 문제인데요.
may be이란 뜻을 허락이란 용어를 빌어는데, 허락이란는 것을 다음과 같이 이해해도 되나요?
"대다수(?) 컴파일러 제작자들이 표준위원회에 요청을 해서 표준위원회에서 승인을 했다, 하지만 표준적 방법은 아니다"라고 생각을 합니다..
//즉, 할수도 있고 못할 수도 있다, 한다면 shall 부분은 지켜야 한다.라고 이해하는 해야 한다고 생각합니다하면 되는건가요.

전웅의 이미지


> > 설사 제가 제안한 내용이 받아들여져 원형 정의에서도 void */char * 가
> > 예외 처리 되었다고 해도 qsort() 와 관련된 문제와는 거리가 있습니다.
> > qsort() 함수의 매개변수는 원형으로 선언됩니다. 따라서 qsort() 함수
> > 내에서의 callback 함수 호출은 항상 원형 선언 아래에서 이루어지게
> > 됩니다. 그러면 이제 문제는 원형 선언과 비원형/원형 정의의 호환 문제로
> > 바뀌게 되며, 이 문제는 비원형 아래에서의 호출과는 달리 function call
> > expression 부분이 아닌 function declarator 부분에서 정의됩니다. 따라서
> > 원형 선언과 비원형/원형 정의의 호환 문제에서 void */char * 를 예외로
> > 두는 것은 type compatibility 규칙에 일관성을 깨는 결과를 가져옵니다.
> >
>
> 우선 함수 호출 표현식에서 인자들이 변환하는 방식에는 두가지가 존재한다는 것을 이해하고 있어야 합니다(모르는 분들에게 있어).
[...]
> 여기서, 중요한 것은 함수 선언(정의가 아닌)으로 인자들을 파라미터 자료형으로 변환한다는 것입니다.
>
> 이 인자들의 변환이 정상적으로 수행되었다면, type compatibility 규칙에 문제가 발생하지 않습니다.
> 하지만, 선언에 대한 변환이기 때문에 정의와의 호환 문제가 남아 있습니다.
>

넵, 맞습니다. 그래서 function call expression 부분이 아니라 function
declrator 부분에서 다루게 됩니다. 즉, 일단 원형 선언 아래에서 함수
호출이 일어나면 더 이상 함수 정의-함수 호출의 관계가 아니라 함수 정의-
함수 선언의 관계가 됩니다.

>
> // 개인적으로 언어의 단점이자 장점으로 생각합니다(실수에 의해 선언과 정의를 다르게 할수 있기에).
>

현 C 언어의 체계에서 마땅히 다른 방법이 쉽게 떠오르지는 않습니다. 이것
이 문제가 되지 않게 하는 유일한 방법은 잘 알려진 coding guideline 을
잘 따르는 것뿐이 없는 것 같습니다.

> 그런 예제(변환되지 않는 경우)는 두가지 형태를 띌수 있습니다.
> 하나는 전웅님이 설명하신 함수 포인터에 대한 호출시 상황이고, 다른 하나는 아래 같은 경우입니다.
>
> extern int foo(int, char, double); /* 주석 1 : 함수원형을 선언합니다. */
>
> void test(void)
> {
>
> (void) foo(0x10, 'c', 3.14D); /* 주석 2: 인자들을 주석 1의 파라미터로 형 변환합니다. */
>
> }
>
> extern int foo(double f64a, double f64b, double f64c) /* 주석 3 : 실행시 파라미터들을 함수 내부에서 접근할(UB) 것입니다. */
> {
> /* do something */
> }
>
>
> 그런 void * / char * 간에 비-원형 함수선언시 어떻게 변환이 되는지를 생각해야 합니다.
>
> 생각을 해 보면 변환 자체를 할 수 없습니다.
> // 비-원형 함수나 원형 함수나 동일합니다.
>
> 그리고, type compatibility 규칙에 일관성을 깨는 결과를 가져옵니다.
> 이것은 전체적으로는 맞는 논리 입니다. 하지만 이 함수 호출 규칙(?)들에서
> 변환하지 못한것에 한정해야 하지 않을까 생각합니다(이부분에 대해서 듣고 싶습니다).
>
> type compatibility 규칙에 깨는 것이라면 wrapper용 함수를 사용한 것은 모순에 해당되는 것이라 생각합니다.
> // void * / char * 간의 변환은 보장 받을 수 없다는 논지가 되버리기 때문입니다.
>

질문을 이해하기에 어려운 부분이 있어, 질문에 답변 드리기 보다는 제가
드렸던 말씀을 자세히 풀어 다시 드리겠습니다.

제가 말씀드린 type compatibility 에 일관성이 깨진다는 이야기는 다음과
같은 이야기입니다.

원형 선언 아래에서의 호출과 비원형 정의 사이에 void */char * 를 호환
되는 것으로 인정하는 것은 (지금의 표준처럼) 단순히 호출과 정의 사이에
예외를 만드는 것이 아니라 선언과 정의 사이에 예외를 만들게 됩니다. 즉,

void func(void *);

void func(c)
char *c;
{ ... }

를 호환되는 것으로 인정하게 된다는 것입니다. 그러면, 이 함수 정의는

void func(char *);

와는 당연히 호환되는 것이기 때문에, 결국

void func(void *);
void func(char *);

를 호환되는 것으로 인정하는 결과를 낳을 수 있습니다. 이와 같은 논리로
계속 진행을 하게 되면, char *, signed char *, unsigned char *, void *
가 모두 호환형이 되어야 하며, 마지막에는 char, signed char, unsigned
char, void 가 모두 호환형이 되어야 한다는 이상한 결론까지 도달할 수
있습니다. 이는 C 언어의 type system 전체에 크나큰 악영향을 주게
됩니다.

물론, 다소 복잡한 예외 규칙을 부분 부분마다 적용해 딱 원하는 정도의
행동을 얻어내는 것이 가능할 수는 있습니다. 하지만, 원형 선언의 근본적
목적을 고려할 때 그와 같은 변화는 결코 가치가 있어 보이지 않으며,
이루어져서도 안 되는 변화라 생각합니다.

> 그리고, 이는 다른 문제인데요.
> may be이란 뜻을 허락이란 용어를 빌어는데, 허락이란는 것을 다음과 같이 이해해도 되나요?
> "대다수(?) 컴파일러 제작자들이 표준위원회에 요청을 해서 표준위원회에서 승인을 했다, 하지만 표준적 방법은 아니다"라고 생각을 합니다.
> //즉, 할수도 있고 못할 수도 있다, 한다면 shall 부분은 지켜야 한다.라고 이해하는 해야 한다고 생각합니다.
>

흠... RFC 문서에 나오는 "shall", "shall not" 등의 표현을 올바르게
이해하기 위해 그와 같은 용어의 의미를 규정해 놓은 RFC 가 별도로 있듯이
ISO 표준도 별도의 문서를 통해 (즉, 표준을 작성하고 편집하기 위한 표준
이라고 생각할 수 있습니다) 표준에 사용되는 "shall", "may", "can" 등의
용어를 규정하고 있습니다 - 이를 ISO/IEC Directives 라고 부릅니다.

Quote:

A pointer to void may be converted to or from a pointer to any
incomplete or object type.

질문이 나온 원인을 제공한 문맥에서의 의미는 다음과 같습니다.

말 그대로 프로그래머는 void * 와 pointer to object/incomplete type
사이에 변환을 하도록 허가된다는 의미며, 따라서 이에 맞춰
implementation 이 그와 같은 변환을 포함하는 프로그램을 표준을 따르는
프로그램으로 인정해 올바르게 지원해야 한다는 뜻입니다.

즉, 컴파일러 제작자들이 요청을 했는지 안 했는지는 알 수 없는 사실이며
(이 경우에는 과거 오래전부터 관례적으로 사용하던 방법을 void * 를 이용
해 분명히한 경우에 해당됩니다), 표준 위원회에서 승인(허가)한 방법
이기에 표준적인 방법이라는 뜻입니다.

제겐 익숙한 부분이다보니 어느 부분에서 오해가 발생하는지 잘 보이지
않습니다. 구체적으로 어느 부분에서 문제가 될 수 있는지 설명해 주시면
보다 자세한 설명 드릴 수 있을 것 같습니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

Quote:

void func(void *);
void func(char *);

호환(?)변환이 안된다면,
Quote:

매개변수 type 을 조정해 줄 수 있는 wrapping 함수를 도입해야 합니다.

도 모순(변환이 안되기 때문에)에 빠집니다.
void func(void * voidPtr) 
{
   char * ptr = (char *) voidPtr;  /* 데이타 손실이 발생할수 있기에 */
}

-- 덧붙이는 글 --
호환이란 용어를 변환이란 용어로 변경합니다.

전웅의 이미지

다시 논의가 원점으로 돌아오는 느낌입니다.
(그렇다면 수사는 다시 원점으로~~~)

님의 논리는

Quote:

function ... with type A 를 function ... with type B 로 변환해
호출하는 것이 불가능하다면 => type A 를 type B 로 데이터 손실 없이
변환할 수 없다.

가 됩니다. 이에 대한 대우는

Quote:

type A 를 type B 로 데이터 손실 없이 변환할 수 있으면, function ...
with type A 를 function ... with type B 로 변환해 호출하는 것이
가능하다.

입니다. 이때 대우가 거짓이면 원래 명제도 거짓입니다.

type A 를 struct s1 *, type B 를 struct s2 * 로 놓으면 type A 에서
type B 로의 변환은 항상 정보 손실 없이 이루어집니다 - 물론, 접근 가능
여부는 별개의 문제입니다. 그렇다면

void func(struct s1 *);

void func(struct s2 *);

로 변환해 호출하는 것도 가능해야 하나요? 이것이 불가능하다는 것이
지금까지 체스맨님과 진행했던 논의였습니다.

따라서 말씀하신 명제는 거짓이 됩니다.

좀 더 일반화해 설명하면, 두 type 사이에 정보 손실 없이 변환이
가능하다고 해서, 그 type 을 각각 매개변수로 갖는 두 함수 사이의 변환
후 호출이 가능한 것은 아닙니다.

이것이 바로 표준의 의도이며, 올바른 해석입니다. 이에 따라 가장 극단적
인 implementation 은 현실적인 제반 조건이 그렇게 변환한 함수 호출이
올바르게 동작하도록 허용하는 경우에도 type tracking 등을 통해 그와
같은 부분을 오류로 잡아낼 수 있게 됩니다.

따라서 아무리 생각해도 현실적인 implementation 에서 그와 같은 구문이
문제를 일으킬 것 같지 않다 (마치 K&R2 의 오류에 대해 DMR 이 변명
했듯이) 라고 판단하신다면, 해당 구문이 표준에 의해 이식성을 보장받지
못한다는 사실만 기억하시고 그대로 사용하시면 됩니다. 하지만, (최소한
OP의 예에서는) 아주 간단한 wrapping function 도입만으로 이식성을 보장
받을 수 있다는 사실 또한 함께 기억하시면 될 것 같습니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

인자 변환후의 내용이기 때문에 맞지 않는 내용입니다.
글을 주의해서 써야하는데, 잘못된점 사과합니다.

현재 진행하고 있는 내용들은 (void */ char *)에 대해서만 해당되는 내용입니다(다른 분들의 오해가 없기를 바랍니다).

Quote:

char * 와 void * 가 같은 표현, 같은 정렬제한을 갖는다는 사실이
보장되기 때문에

다음 코드에 동일하게 동작한다고 보고 있습니다.
extern int foo(charPtr)          /* 주석 1:  비-원형 함수 */
char * charPtr;
{
     return 0;
}
 
int test (void)
{
    void * voidPtr;
 
    /* do something */
 
    (void) foo(voidPtr);      /* 주석 2 : 변환을 할 수 없습니다. */
                                    /* 이것이 예외상황에 해당된다고 보고 얘기하고 있는것 입니다. */
#if 0
    (void) foo((char *) voidPtr);    
#endif
 
}


extern int foo(void * charPtr);
int test (void)
{
    void * voidPtr;
 
    /* do something */
 
    (void) foo(voidPtr);      
}
extern int foo(char * charPtr)
{
     return 0;
}

제가 잘못 이해하고 있는 부분이 있으면 지적 부탁드립니다.

--덧붙이는 글--
매번 자세한 설명 감사드립니다.

전웅의 이미지

> 다음 코드에 동일하게 동작한다고 보고 있습니다.
>

extern int foo(charPtr)          /* 주석 1:  비-원형 함수 */
char * charPtr;
{    return 0;   }
 
int test (void)
{
    void * voidPtr;
    foo(voidPtr);      /* 주석 2 : 변환을 할 수 없습니다. */
                                    /* 이것이 예외상황에 해당된다고 보고 얘기하고 있는것 입니다. */
}

예, C99 에 주어진 "예외"에 의해 허락되는 행동입니다.

extern int foo(void * charPtr);
int test (void)
{
    void * voidPtr;
    foo(voidPtr);      
}
 
extern int foo(char * charPtr)
{    return 0;   }

아닙니다. 직접 컴파일해보시면 아시겠지만, ... foo(void *) 와
... foo(char *) 가 호환되는 type 이 아니기 때문에 선언과 정의 부분에서
부터 문제가 됩니다. 설사 함수 정의부를 다른 소스 파일로 옮겨 컴파일러
가 이를 감지해 내지 못하게 한다고 해서 정의된 행동을 보장받는 것이
아닙니다. 이전 논의에서도 몇번 언급되었듯이, void */char * 에 대한
예외는 비원형 정의-비원형 호출에 대해서만 적용됩니다. 이를 다른 경우로
확대 해석하는 것은 잘못된 것입니다.

흠... 아마도 오해의 시작은 저 위의 코드인 것 같습니다. 일단 표준에 저
위의 코드를 허용해주는 void */char * 예외 부분이 없다고 생각하시기
바랍니다. 그것이 가장 자연스러운 상태입니다.

동일한 표현/정렬제한이 type algebra 상에서의 호환성을 보장해주지는
않습니다. 이는 지극히 당연한 이야기입니다. 예를 들어, 어떤 구현체가
float 과 double 을 동일하게 IEEE double precision 으로 지원한다고 해서
(같은 표현/정렬제한 보장) 해당 구현체에서 int func(float) 과 int
func(double) 이 호환되는 type 이 될 수 없는 것과 마찬가지입니다.

void * 와 char * 는 다른 type 이지만, 표준의 필요에 의해 같은 표현과
정렬제한을 갖도록 예외적으로 결정된 것 뿐입니다.

따라서 "기본적으로" foo(void *) 와 foo(char *) 는 호환될 수 없는 type
입니다 - 이것이 C90 의 상태입니다.

그런데, 표준의 footnote 중에는 "같은 표현/정렬제한"이 자료를 상호 호환
해 주고 받을 수 있게 하기 위한 "의도"로 주어진 것이라는 부분이
있었습니다 (C99 에도 그대로 남아 있습니다). 그러다보니 사람들은 혼란을
느끼기 시작했습니다. 한쪽에서는 같은 표현/정렬제한이더라도 호환 type
이 아니기 때문에 (예를 들면) 가변 인자 등으로 전달할 때 주의해야 한다
고 하면서, 다른 한쪽(footnoet)에서는 상호 호환해 주고 받을 수 있다고
표현되어 있는 것입니다.

이에 대한 위원회의 공식적인 답변은 footnote 는 어디까지나 비공식적인
정보를 제공하기 위한 부분, 즉 normative part 가 아니라 informative
part 이기 때문에 footnote 의 내용은 말 그대로 "의도"를 포함하는 것일뿐
"보장"을 담고 있는 것은 아니라는 것이었습니다. 따라서 엄밀한 의미에서
여전히 void */char * 는 구분된 type 이며 (예를 들어) 가변 인자로
char * 를 전달하고 함수 안에서 void * 로 받는 것도 잘못된 행동인 것이
었습니다.

그런데, C99 에 들어오면서 (제 기억이 맞다면) Clive 가 일부 경우에는
C90 의 footnote 에서 명시했던 사항을 공식적으로 보장해 줄 필요가 있다
는 주장이 제기되었습니다. 따라서 기존 type compatibility 규칙에 영향을
주지 않는 "비원형" 호출 문맥 (저 역시 이때 함수 정의의 원형/비원형
형태와는 무관하게 보장이 되어야 한다고 생각하지만 어쨌든 위원회는
비원형 함수 정의에 대해서만 보장해주었습니다), 그리고 가변 인자 문맥
(마찬가지로 printf 계열 함수에도 동일하게 보장이 되어야 한다고 생각
하지만 위원회의 의도는 불분명합니다) 에서 일부 경우의 정수형과 void */
char * 에 대해서 예외적으로 비호환 type 을 전달할 수 있도록 허용해 준
것입니다.

따라서 두번째 예가 나와 있는 원형 선언-원형 호출-원형 정의 문맥에서는
그와 같은 예외가 결코 적용되지 않습니다. 그와 같은 예외가 함수 호출에
서 유일하게 적용되는 경우는 비원형 호출-비원형 정의 문맥뿐입니다.

가장 근본적으로는 같은 표현/같은 정렬제한이라는 추가적 특성에 집착해
서로 구분해야 하는 비호환 type 을 호환된다고 생각하셔서는 안 됩니다.
이 과정에서 가장 중요한 것은 현실적인 특정 구현체를 바탕으로 언어 구현
을 생각하셔서는 안 된다는 것입니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

답변 감사합니다.

여기부터는 잡생각입니다.

컴파일러를 구현하는 측면에서 본다면 동일한 코드(기계어)가 생성될 것입니다.수 있지 않을까 생각합니다.
//삭제한 이유는 함수 정의의 관점에서 파라미터의 구현이 어떻게 될지는 몰라서 지웠습니다.

다르게 구현될 수 있는건가요?

전웅의 이미지

> 답변 감사합니다.
>
> 여기부터는 잡생각입니다.
>
> 컴파일러를 구현하는 측면에서 본다면 동일한 코드(기계어)가 생성될 것입니다.
>
> 달리, 말할 수 있는 부분이 없습니다.
>
> 다르게 구현할 수 있는 방법이 있다면, 좀 설명 부탁드립니다.
>

struct s1 *, struct s2 * 문제에 대해서는 layout 이외에 최적화의 문제가
관여될 수 있음은 이미 말씀드렸습니다.

하지만, void */char * 의 경우에는 저 역시 구현적 측면에서 뚜렷한 차이
가 있을 수 있다고 말씀드리기 어렵습니다. 그래서 제가 여러번

Quote:

따라서 아무리 생각해도 현실적인 implementation 에서 그와 같은 구문이
문제를 일으킬 것 같지 않다 (마치 K&R2 의 오류에 대해 DMR 이 변명
했듯이) 라고 판단하신다면, 해당 구문이 표준에 의해 이식성을 보장받지
못한다는 사실만 기억하시고 그대로 사용하시면 됩니다.

와 같은 말씀 드린 바 있습니다. 만약 누가 보기에도 분명한 문제가 발생할
수 있는 상황이었다면 까칠한(^^) 제가 저 정도로 말씀드리지는 못했을
것입니다.

제가 유일하게 생각할 수 있는 것은 (이미 말씀드렸듯이) interpreter
구현체 정도에서 인자 전달시에 type 정보 함께 전달해 type mismatch 를
잡아주는 경우입니다.

이런 점에서 아마도 표준이 void * 를 좀 더 비밀스러운 type 으로
만들어도 큰 무리가 없지 않았을까 싶습니다 - 즉, char * 를 포함해 모든
type 의 대상체 포인터를 정보 손실 없이 담을 수 있다는 사실만 보장해도
충분했으리라 생각합니다 - 그렇다면 void * 와 char * 의 유난히 밀접한
관계 때문에 발생하는 여러 오해가 애초부터 없을 수 있지 않았을까 생각
합니다.

하지만 아마도 과거 void * 가 없던 시절 대신 char * 를 사용했던
프로그램의 행동을 꾸준히 보장해주기 위해 void * 와 char * 사이의 밀접
한 관계를 명시한 것이라 추측됩니다.

단순히 두 type 이 구현적 측면에서 동일하다고해서 이 둘을 보다 추상적인
단계의 type system 에서도 무리없이 호환될 수 있는 type 으로 본다면 더
이상 type system 의 존재 이유가 없을 것입니다. 어차피 저 밑바닥에는
비트열만이 존재할 뿐입니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

Quote:

단순히 두 type 이 구현적 측면에서 동일하다고해서 이 둘을 보다 추상적인
단계의 type system 에서도 무리없이 호환될 수 있는 type 으로 본다면

동일하다고 한다면 그 컴파일러에서는 동일하게 동작하는 것이 정상적이라 생각합니다.

그리고, 단지 (비원형 정의 함수와 원형 정의 함수에서)같게 행동해야 한다는 사상에서 이 논의를 출발한 것 뿐입니다.

전웅의 이미지

> >
> > 단순히 두 type 이 구현적 측면에서 동일하다고해서 이 둘을 보다 추상적인
> > 단계의 type system 에서도 무리없이 호환될 수 있는 type 으로 본다면
> >
>
> 동일하다고 한다면 그 컴파일러에서는 동일하게 동작하는 것이 정상적이라 생각합니다.
>

컴파일러에서는 동일하게 동작할 수 있습니다. 하지만, 이를 개념을 통해
설명하는 표준에서는 여전히 구분되는 것으로 다룰 수 있습니다.

국제 초코바 자판기 표준(ICVMS ^^)에서 초코바 자판기는 최소한 "가장
맛있는 초코바"와 "가장 인기있는 초코바" 버튼을 만들어 제공하라고 요구
했다고 가정해보겠습니다. 그런데 가장 맛있는 초코바로 알려진 "감금시간"
이 가장 높은 인기를 얻게 되었습니다. 따라서 자판기 구현 업체에서는
원가 절감과 효율성 제고를 위해 "가장 인기있는 초코바"와 "가장 맛있는
초코바" 버튼 뒤의 회로를 사실상 동일하게 만들어 두었습니다. 그렇다고
해서 "가장 맛있는 초코바"와 "가장 인기있는 초코바"의 "개념"까지 동일해
지는 것일까요?

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

그렇군요. 개념이 분명히 다르군요(글을 직관적으로 서술하지 못했군요// 제글에 대한 독백입니다).

초코바 자판기를 만든다면, 다음과 같이 만들겠습니다.

1. 초코바 자판기를 만든다.
2. 회사에 있는 초코바의 종류는 다음과 같다.
// 딸기 초코바, 바나나 초코바 이 둘 밖에 없다.
3. 두 제품에 대한 버튼은 만들수 있다.
4. 가장 맛있는 초코바 버튼이 필요하다.
5. 가장 인기있는 초코바 버튼도 필요하다.
6. 가장 맛있는 초코바와 가장 인기있는 초코바 버튼을 만들수 있는가?
// 회사 초코바가 모두 맛있어서 만들수 없다.
7. 그럼 어떻게 할 것인가?
8. 회사에서 제공하는 두 제품에 대한 버튼만 만든다.
// 자판기에 양해성 글을 쓰고 나머지는 고객에게 맏긴다.

지루함이 계속되는것 같아 이만 끝맺을것이 좋지 않을까 생각합니다.

-- 덧 붙이는 글 --
지금까지 인자 전달(Parameter Passing)에 대해 전웅님께서 이야기해 주셨습니다.
다시 한번 감사합니다.

전웅의 이미지

[열악한 네트웍 환경으로 동일한 글이 2개 올라가는 탓에
하나의 본문을 지웁니다. 관리자님께서 혹시나 이 글
보시면 적절히 처리해주세요]

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

doodoo의 이미지

한번 프로그램을 직접 컴파일 해 봐야 하겠군요...
되는지...

전웅의 이미지

어디서 봤던 질문에 어디서 했던 답변이라 생각했었습니다.
그리 멀지 않은 곳에 있네요.

http://kldp.org/node/3877

더불어 C FAQs 도 인용합니다.

http://c-faq.com/lib/qsort1.html

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

익명사용자의 이미지

나는 중국 학생 oh 환영
웹사이트를 번역하는 것을 있을 것이다 여기에 올 것이다 모두http://blog.sina.com.cn/u/1238077555
나의 중국 blog 또한, 원한다 메시지 oh를이다http://www.worldlingo.com/en/products_services/worldlingo_translator.html

체스맨의 이미지

아래쪽에 댓글 보기 옵션에 "그냥보기 - 펼침" 을 사용하면 전웅님께서 원하시는대로 보이지 않을까 합니다.

우선 다음 내용이 전웅님께서 주장하시는바가 맞는지 확인바랍니다. 이것이 주장하시는 바가 아니라 완전한 사실이라면 토론할 필요는 없습니다. 사실을 가지고 토론하는 것은 말이 안되지요.

구조체끼리는 정렬 제한이 같아서 서로 캐스팅할 수 있고 이것은 표준에 위배되지 않는다.
 ( 동일한 크기와 순서로 멤버가 배치되어 있는지는 여기서 얘기할 필요가 없을 것 같습니다. 캐스팅해서 쓰는 경우 당연히 그래야하는 것이니까요. 캐스팅만 주안점을 두고 얘기했으면 좋겠습니다.)
 
그러나, 서로 다른 구조체를 인자로 갖는 함수를 캐스팅하는 것은 표준 위반이다. ( 예제는 위에 제시했습니다. )

위의 내용이 전웅님께서 주장하시는 바에서 벗어나지 않는지요? 그 경우, 제 생각에는 표준 문서의 다음 부분이 그에 대한 근거가 되는 것으로 보이지는 않습니다.

If the function is defined with a type that is not compatible
with the type (of the expression) pointed to by the expression
that denotes the called function, the behavior is undefined. 

이 부분에 대한 의견 부탁드립니다.

-------------------------------------------------------

Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

KLDP BBS 의 새 시스템이 영 맘에 들지 않아 뉴스그룹 스타일로
인용합니다. 이해 바랍니다.

> 아래쪽에 댓글 보기 옵션에 "그냥보기 - 펼침" 을 사용하면 전웅님께서 원하시는대로 보이지 않을까 합니다.
>

감사합니다 - 그런데 펼쳐 놓으니 시간 순으로 배열되어 편하긴 한데 인용
부분이 없어 attribution 파악이 어렵군요 - ㅋㅋ 도움은 안 되면서 불만만
많습니다.

> 우선 다음 내용이 전웅님께서 주장하시는바가 맞는지 확인바랍니다. 이것이 주장하시는 바가 아니라 완전한 사실이라면 토론할 필요는 없습니다. 사실을 가지고 토론하는 것은 말이 안되지요.
>
> 구조체끼리는 정렬 제한이 같아서 서로 캐스팅할 수 있고 이것은 표준에 위배되지 않는다.
> ( 동일한 크기와 순서로 멤버가 배치되어 있는지는 여기서 얘기할 필요가 없을 것 같습니다. 캐스팅해서 쓰는 경우 당연히 그래야하는 것이니까요. 캐스팅만 주안점을 두고 얘기했으면 좋겠습니다.)
>
> 그러나, 서로 다른 구조체를 인자로 갖는 함수를 캐스팅하는 것은 표준 위반이다. ( 예제는 위에 제시했습니다. )
>
>
> 위의 내용이 전웅님께서 주장하시는 바에서 벗어나지 않는지요? 그 경우, 제 생각에는 표준 문서의 다음 부분이 그에 대한 근거가 되는 것으로 보이지는 않습니다.
>

체스맨님의 관심사는 특정 코드가 표준의 이식성 보장을 받느냐 아니냐인
것으로 이해하고 있습니다. 따라서 표준을 기준으로 설명드립니다 -
"에이.. 이 세상에 그런 시스템이 어디 있느냐"는 반론은 받지 않습니다.

정리하겠습니다.

- 구조체 포인터의 정렬제한과 표현은 같습니다. 따라서 구조체 포인터의
변환 자체는 문제를 일으키지 않습니다.

- 하지만 그렇게 변환한 구조체 포인터로 멤버를 접근하는 것은 별개의
문제입니다 (즉, 변환이 허락된다고 해서 그 포인터를 사용해 대상에
접근하는 것까지 허락되는 것은 아닙니다). 동일한 크기와 순서로 멤버가
선언되어 있다고 해도 구조체 포인터를 다른 구조체형으로 변환해 멤버에
접근하는 것은 허락되지 않습니다. 한가지 예로 서로 다른 구조체형은
동일한 멤버를 동일한 순서로 포함해도 서로 다른 type 이기 때문에 멤버
간의 padding 을 다르게 가질 수 있습니다. 이러한 문제를 해결하기 위해
표준이 도입한 것이 common initial sequence 입니다. 즉, 그와 같이 앞
의 일정 부분을 동일한 멤버를 갖는 구조체를 공용체로 묶어 줌으로써
공용체 멤버로 포함되어 있는 두 구조체의 layout 을 동일하게 맞춰주고
포인터를 변환해 멤버에 접근해도 항상 의도된 행동을 얻을 수 있도록
해주는 것입니다 - 제가 이전 글에서 공용체 이야기를 꺼낸 이유가 바로
여기에 있습니다.

- 그리고 단순히 서로 다른 구조체형을 인자로 갖는 함수를 캐스팅 하는 것
자체는 표준 위반이 아닙니다 (이미 말씀드린 바 있습니다). 하지만,
그렇게 변환된 함수를 호출하는 것은 또 다른 문제입니다. 이때 아래
사항이 적용됩니다.

Quote:

If the function is defined with a type that is not compatible
with the type (of the expression) pointed to by the expression
that denotes the called function, the behavior is undefined.

int func1(struct t1 *param)
{
    /* do something */
}
 
int (*func2)(struct t2 *);
func2 = (int (*)(struct t2 *)) &func1;    /* okay */
 
struct t2 bar;
func2(&bar);    /* wrong */

인 경우를 생각해 보겠습니다. "정의"된 함수의 type 은

func1: function returning int with struct t1 *

입니다. 반면, func1 를 "호출"하는데 사용된 함수 포인터 func2 의 type
은 (pointer to 를 제외하면)

*func2: function returning int with struct t2 *

입니다. 저 문장이 말하는 것이 이 두 type 이 호환되어야 한다는
것입니다.

이제 반환형과 매개변수 type 이 모두 호환되어야 함수도 호환됩니다.
하지만 struct t1 * 와 struct t2 * 는 호환이 되지 않습니다. 따라서
두 함수형도 호환이 되지 않습니다. 따라서 저 위의 인용된 문장을 위반
하게 되어 UB 가 됩니다.

지금까지 언급된 문제는 모두 제가 인용한 부분 때문에 표준의 보장을
받지 못하는 것이며, 제게 보여주신 코드는 분명 잘못된 코드입니다.
이는 엄연한 "사실"이며 다르게 해석할 여지가 없습니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

- 하지만 그렇게 변환한 구조체 포인터로 멤버를 접근하는 것은 별개의
문제입니다 (즉, 변환이 허락된다고 해서 그 포인터를 사용해 대상에
접근하는 것까지 허락되는 것은 아닙니다).

이 부분은 당연한 부분이라, 언급이 안되었으면 하는 것입니다. 글이 자꾸 길어지고
논점이 빗나가는 경향이 있기 때문입니다.

전웅님의 글에서 제가 논의하고자하는 부분은 다음 내용뿐입니다.

저 문장이 말하는 것이 이 두 type 이 호환되어야 한다는
것입니다.
 
이제 반환형과 매개변수 type 이 모두 호환되어야 함수도 호환됩니다.
하지만 struct t1 * 와 struct t2 * 는 호환이 되지 않습니다.

여기서, 핵심 "호환되는 타입" 인가 하는 문제를 논하고 싶은 것입니다.
전웅님 답변에는 항상 곁가지가 있고, 그것은 이미 당연한 것으로 전제된 것이 많아서
때로는 논점이 흐려지거나, 어떤 면에서는 좀 답답한 부분도 있네요.

이미 전웅님께서 모든 구조체의 포인터는 정렬 제한이 같다고 언급하셨고,
저는 이것을 '호환되는 타입' 이라는 의미로 받아들였습니다.
표준상에서는 분명히, 'same' 이라 하지 않고 'compatible' 이라 되어있습니다.

이 부분을 명확히 하실 필요가 있습니다. 받아들이기는 어렵습니다만,
전웅님 말씀에 의하면, 구조체 포인터들은 정렬 제한이 같으나 호환되는 타입은 아닌 것입니까?

C 언어에서는 분명히 함수 포인터 캐스팅을 지원합니다. 하지만, 전웅님 말씀대로라면,
이 함수 포인터 캐스팅은 어디에도 쓸 곳이 없습니다. 결국 표준을 위배하도록 만드는
함수 포인터 캐스팅 문법은 왜 C 표준인지요?

---------------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

doldori의 이미지

제가 전웅님은 아니지만... :-)

Quote:

이미 전웅님께서 모든 구조체의 포인터는 정렬 제한이 같다고 언급하셨고,
저는 이것을 '호환되는 타입' 이라는 의미로 받아들였습니다.
표준상에서는 분명히, 'same' 이라 하지 않고 'compatible' 이라 되어있습니다.
이 부분을 명확히 하실 필요가 있습니다. 받아들이기는 어렵습니다만,
전웅님 말씀에 의하면, 구조체 포인터들은 정렬 제한이 같으나 호환되는 타입은 아닌 것입니까?

표준에서 말하는 compatible은 실은 same이나 같은 뜻이라고 볼 수 있습니다.
각각의 형마다 부가적으로 붙는 조건들 때문에 same 대신 compatible이라는
용어를 구분해서 사용한 것 같습니다.
Quote:

6.2.7/1
Two types have compatible type if their types are the same. [...]

6.7.5/2
For two pointer types to be compatible, both shall be identically qualified
and both shall be pointers to compatible types.

6.7.7/5
EXAMPLE 2 After the declarations

typedef struct s1 { int x; } t1, *tp1;
typedef struct s2 { int x; } t2, *tp2;

type t1 and the type pointed to by tp1 are compatible. Type t1 is
also compatible with type struct s1, but not compatible with the types
struct s2, t2, the type pointed to by tp2, or int.


제가 보기에 체스맨님께서는 compatible한 형이 되기 위한 조건으로
정렬 제한만을 생각하시는 것으로부터 오해가 비롯된 것이 아닌가 싶습니다.
결국 처음에 예로 드신 두 구조체는 compatible한 형이 아닌 것입니다.
그 점에 대해서는 전웅님께서 이미 충분히 설명하신 듯 합니다만...
체스맨의 이미지

C99 표준상으로는 그렇습니다.
하지만, C89 표준상으로는 다음에 토론에 언급되는 것처럼,

http://www.velocityreviews.com/forums/t318198-declaration-of-structs-inside-parameter-list.html

Two types have compatible type if their types are the same.
Additional rules for determining whether two types are compatible are
described in $3.5.2 for type specifiers, in $3.5.3 for type
qualifiers, and in $3.5.4 for declarators. Moreover, two
structure, union, or enumeration types declared in separate
translation units are compatible if they have the same number of
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
members, the same member names, and compatible member types; for two
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
structures, the members shall be in the same order; for two
^^^^^^^^^^
enumerations, the members shall have the same values.

이렇습니다. 물론 C99 가 최신 표준이며, 이 부분에서 C89 와 C99 가 호환되지 않는 측면이겠지요. 이 토론에도 C99 에 왜 그런 내용이 추가되었는지 이해되지 않는다는 말이 있습니다.

이 부분에서 호환을 논하자면, C++ 의 계승 구조에 대해서는 어떻게 생각하지는지요?

class A {
    int a;
};
 
class B : public A {
    int b;
};
 
class B* b = new B;
class A* a = b;
a->a = 1;

단지 C 와 C++ 이 다른 언어이기 때문이다라고 생각하시는지요?

구조체의 포인터를 호환성 있는 타입으로 간주하는 무수히 많은 코드들이 현존합니다. 게다가 C89 에서 그것은 표준이었구요. '표준'이 '옳다'를 의미할 수는 없습니다. 수정되어야 하는 부분도 반드시 있는 것이구요.

----------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

doldori의 이미지

C99에 추가된 tag까지 같아야 compatible한 형이라는 내용이 이전에는 없는 것이었군요.
저도 표준에 정통하지 못해서 그런 내용이 추가된 이유는 잘 모르겠습니다. ^^;
어쨌든 이 논의의 초점인 위의 두 구조체가 compatible한 형인지에 대해서는 논란의 여지가 없겠지요.

Quote:

이 부분에서 호환을 논하자면, C++ 의 계승 구조에 대해서는 어떻게 생각하지는지요?
[...]
단지 C 와 C++ 이 다른 언어이기 때문이다라고 생각하시는지요?

네. 저는 다른 이유를 생각할 수 없군요.
C++에서 A와 B는 reference-compatible한 형입니다.

Quote:

구조체의 포인터를 호환성 있는 타입으로 간주하는 무수히 많은 코드들이 현존합니다. 게다가 C89 에서 그것은 표준이었구요. '표준'이 '옳다'를 의미할 수는 없습니다. 수정되어야 하는 부분도 반드시 있는 것이구요.

음... 주장하시는 바가 뭔지 잘 모르겠습니다. 현행 표준에 비추어 정의되지 않은 행동을 유발하는
코드가 현존한다는 것과 C89는 지금과 달랐다는 것이 지금 논의와 별로 관계있는 것 같지도 않고요.
체스맨의 이미지

아, 그리고 위에도 언급했지만,
C99 표준에 따르면, 함수 포인터 캐스팅 문법을 사용하는 것은
항상 표준을 위배하게 될 것으로 예상되지만, 여전히 함수
포인터 캐스팅 문법 자체는 표준인 것에 대해 어떻게 생각하시는지요?

--------------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

저도 전웅님은 아니지만 끼어들겠습니다^^;

> 이 부분에서 호환을 논하자면, C++ 의 계승 구조에 대해서는 어떻게 생각하지는지요?

class A {
    int a;
};
 
class B : public A {
    int b;
};
 
class B* b = new B;
class A* a = b;
a->a = 1;

> 단지 C 와 C++ 이 다른 언어이기 때문이다라고 생각하시는지요?

이건 상속된 클래스이지 않습니까. OOP 언어인 C++과 그렇지 않은 C와의 비교는 의미가 없습니다. 그리고 C에서는 두 구조체 s1과 s2의 연관성을 언어 자체적으로 표현할 방법이 없습니다.

> 아, 그리고 위에도 언급했지만, C99 표준에 따르면, 함수 포인터 캐스팅 문법을 사용하는 것은 항상 표준을 위배하게 될 것으로 예상되지만, 여전히 함수 포인터 캐스팅 문법 자체는 표준인 것에 대해 어떻게 생각하시는지요?

함수 포인터 끼리의 변환과 그 포인터를 이용한 호출을 하나로 묶어서 생각하시는 듯 합니다. C99의 한 부분입니다.

> A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

포인터 끼리의 변환은 가능하지만, 그걸로 함수의 호출을 할 수는 없습니다. 함수 포인터로 원하는 함수를 호출하기 위해서는 원래의 타입으로 형변환을 한 후에 써야 합니다. void * 쓰는 것과 마찬가지입니다. 따라서 함수 포인터 캐스팅은 꼭 필요하며, 유용합니다.

위에서 언급하시고 계신 문제에서, 두 구조체 s1과 s2의 각각의 내부 표현방식이 어떻게 될 것인지 보장이 되지 않으므로, func2로 func1을 호출하는 것이 undefined로 정해지는게 표준의 입장에서는 당연한 일이라고 생각합니다.

ps. 그나저나 quote는 왜 갑자기 안되는 건지... 얼마전까지만 해도 되던거 같은데...

체스맨의 이미지

익명님이 말씀하신 함수 포인터 캐스팅의 용도는 함수 포인터 캐스팅이 아닌 방법으로도 충분히 커버 가능합니다. 애초에 void* 등으로 캐스팅을 안하면 될 일이니까요. 아니면, 반드시 필요하게 되는 타당한 예제를 들어주시면 반박해보겠습니다.

C99 에서 compatible 의미에 제약을 가했다면 오히려 표준 이탈을 유발할 수 있는 함수 포인터 캐스팅 자체를 없애는게 낫겠지요.

주장하는 바는 C99 가 최신 표준이어도, 이 논의에 대한 실질적 표준(de facto)은 C89 가 맞다는 것입니다. C99 에서 가한 제약이 타당한 근거를 가질 수 없다면, 표준이 틀렸다고 볼 수 있다는 것입니다.

즉, C99 상으로는 호환 없는 타입일지모르나, C89 상으로는 호환 되는 타입이며, 이 둘 중 무엇이 옳으냐는 단지 C99 가 최신 표준이라는 이유로 결정될 수 없다는 것이구요.

C++ 을 예로 든 것은, C++ 의 계승 구조에 대한 내부적 형태는 결국 C89 에서 정의하는 호환성있는 구조체에 대한 언급과 동일하기 때문입니다.

-----------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

아래의 두 인용글은 체스맨님께서 쓰신 내용입니다.

Quote:

C 언어에서는 분명히 함수 포인터 캐스팅을 지원합니다. 하지만, 전웅님 말씀대로라면,
이 함수 포인터 캐스팅은 어디에도 쓸 곳이 없습니다. 결국 표준을 위배하도록 만드는
함수 포인터 캐스팅 문법은 왜 C 표준인지요?

Quote:

익명님이 말씀하신 함수 포인터 캐스팅의 용도는 함수 포인터 캐스팅이 아닌 방법으로도 충분히 커버 가능합니다. 애초에 void* 등으로 캐스팅을 안하면 될 일이니까요. 아니면, 반드시 필요하게 되는 타당한 예제를 들어주시면 반박해보겠습니다.

왜 쓸곳이 없다고 생각하시는지 모르겠습니다. winapi에서 dll 안의 함수를 불러올 때,
GetFuncAddress 함수를 호출하는데, 이 함수의 리턴값의 타입은 FARPROC로 다음과 같습니다.

typedef int (FAR WINAPI *FARPROC)();

이걸로 받은 함수의 주소값을 그냥 사용하지 않고, 다음과 같이 함수 포인터 캐스팅을 해서 호출합니다.

int pFunc(int, int);
 
pFunc = (int (*)(int, int))GetProcAddress(hInst, "AddInteger");
wsprintf(str, "1+2 = %d", (*pFunc)(1,2));

위의 코드는 함수 포인터 캐스팅이 실제로 사용된 예제이며, C99의 표준을 어기지 않고도 유용하게 잘 쓰고 있습니다.

어떤 점에서 함수 포인터 캐스팅이 쓸 곳이 없다고 생각하셨는지 궁금하군요.

체스맨의 이미지

익히 알고 있는 함수입니다만,
다음과 같은 코드는 C99 표준 위배가 아니라고 주장하시는 것인지요?

int f1( struct _s1* s1 );
int (* f2)( struct _s2* s2 ) =
    (int(*)(struct _s2*)) (FARPROC) f1;

----------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

체스맨의 이미지

윗글은 지우지 않고 취소하겠습니다. 잠시 착각했고
GetProcAddress 나 dlsym 구현은 C99 표준 위배에 문제가 없습니다.
이 부분에 캐스팅 용도는 인정합니다. 제가 간과한 부분입니다.

------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지


> dlsym 구현은 C99 표준 위배에 문제가 없습니다.

dlsym 구현은 표준을 따르지 않습니다. void * 는 함수 포인터를 이식성
있게 담을 수 없습니다. union 을 썼어야 하는 부분입니다.

이 역시 bind API 와 마찬가지로 bad design 중에 하나입니다 - 상이라도
하나 줘야 될려나 봅니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

dlsym 이 위배면, GetProcAddress 도 위배입니다.
dlsym 은 미리 정의할 수 없는 함수 타입을 리턴해주어야 하는데 어떻게 union 을 쓸 수 있는지요? malloc 과 같은 상황입니다.

-----------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

함수 포인터끼리의 변환과 역변환은 되지만, 함수 포인터가 아닌 포인터와 함수 포인터 사이의 변환은 정의되지 않은 동작입니다. void*를 범용 함수 포인터로도 쓰는 일이 많은데, 좋지 못한 방식입니다.

전웅의 이미지

union 으로 generic 함수 포인터로 쓸 함수 포인터형과
void * 를 묶어 경우에 따라 필요한(즉, 실제 반환되는 값에
맞는) type 으로 반환값을 받아 사용하도록 고쳐야 합니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

익명님 말씀대로 함수 포인터를 리턴하는 것으로 해결될 문제면,
dlsym 은 큰문제가 아닐 것 같습니다. 리턴값만 헤더에서
바뀌면 , 호출측의 변화는 없을테니까요.

--------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

dlsym() 은 함수뿐 아니라 object 에도 접근할 수 있게 해줍니다. 즉,
dlsym() 을 통해 요청한 대상이 함수라면 함수 포인터가 반환되고, object
라면 대상체 포인터가 반환됩니다. 따라서

int *p = (int *)dlsym(handle, "my_int_variable");

와 같은 호출도 가능합니다. 함수 포인터 - 대상체 포인터 사이의 변환은
정의되지 않기 때문에 dlsym() 의 반환값을 단순히 함수 포인터형으로
바꾸는 것은 문제를 해결하지 못합니다.

따라서 union 으로 묶어 호출하는 쪽에서 대상에 맞춰 원하는 type 의 멤버
를 접근해 사용하거나,

가상의 예:

int *p = (int *)dlsym(handle, "object").obj;
int (*f)() = (int (*)())dlsym(handle, "func").func;

혹은 POSIX 에서 함수 포인터/대상체 포인터로 문제 없이 변환될 수 있는
어떤 추상 type 을 도입해 주는 것입니다.

가상의 예:

posix_magic_t dlsym(void *, const char *);

전자는 기존 코드에 가해지는 영향이 큰 대신 표준 C 언어의 범위 안에서
이식성을 보장 받으며, 후자는 기존 코드에 가해지는 영향이 없는 대신
POSIX 가 환경에 별도의 요구를 하게 됩니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

GetProcAddress 처럼 함수 포인터를 리턴하면 해결되는 문제인가요?

--------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

맞습니다. 따라서 dlsym은 좋지 않은 구현이고 GetProcAddress는 올바른 구현입니다.

전웅의 이미지

dlsym 은 대상체 포인터, 함수 포인터를 모두 반환할 수 있습니다.
따라서 다른 접근이 필요합니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

익명사용자의 이미지

가리키는 것만으로는 문제가 발생하지 않습니다. 문제가 발생하는건 포인터로 다른 타입의 함수를 호출할때입니다. 이미 위에서 몇번이나 설명이 된 사항입니다만.

체스맨의 이미지

예, 그 부분은 잠시 착각이었습니다.

-------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

doldori의 이미지

Quote:

익명님이 말씀하신 함수 포인터 캐스팅의 용도는 함수 포인터 캐스팅이 아닌 방법으로도 충분히 커버 가능합니다. 애초에 void* 등으로 캐스팅을 안하면 될 일이니까요. 아니면, 반드시 필요하게 되는 타당한 예제를 들어주시면 반박해보겠습니다.

void*가 generic pointer라고는 하지만 그것은 어디까지나 대상체(object)를 가리키는 데에 국한된 것이지
함수를 가리키는 포인터로도 쓰일 수 있는 것은 아닙니다. 표준은 void*와 함수 포인터 사이의 변환을
정의하지 않습니다. 당연히 이들간의 캐스팅 역시 허용하지 않습니다.
체스맨의 이미지

한가지 예를 들겠습니다.
소켓 함수들은 de facto 표준입니다.
이 함수들 중 bind 함수는 대개 다음과 같이 호출됩니다.

bind(server_sock, (struct sockaddr *) &myname,
          sizeof(myname));

C89 표준으로는 문제가 없지만, C99 표준상으로는 태그가 다르기 때문에
UB 일 것입니다.

C99 가 제정된지 7년이 지난 지금도 C99 스펙을 완전히 커버하는
컴파일러, 특히 상용 컴파일러가 나오지 않고 있는 상황입니다만,
어쨌든 일정 기간이 더 지나, C99 가 자리를 잡는다면 위와같은 류의
API 들은 수정이 되어야 하고, 기 작성된 코드들도 영향을 받게 되겠지요.

------------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

Quote:

C89 표준으로는 문제가 없지만, C99 표준상으로는 태그가 다르기 때문에
UB 일 것입니다.

C99상으로도 UB고 C89로도 아마 UB입니다.

sockaddr과 sockaddr_in은 보통 다음과 같이 정의됩니다.

struct sockaddr
{
    unsigned short sa_family;
    char sa_data[14];
};
 
struct in_addr
{
    unsigned long s_addr;
}
 
struct sockaddr_in
{
    unsigned short sin_family;
    unsigned short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

간단하게 short가 16bit가 아니거나 long이 32bit가 아니기만 해도 정의되지 않은 동작입니다.
그렇지 않다 하더라도 구조체 맴버 변수의 정렬제한이 16bit보다 크기만 해도 마찬가지입니다.
이런 문제를 피하는 방법은 C 표준 내에선 없고, 컴파일러가 각자 지원하는 #pragma pack 같은
방법을 써야만 합니다.

bind함수는 C99의 관점에서 보면 이식성이 없는 함수입니다. 하지만 C99만 가지고 프로그래밍을
할 수 있는 사람은 없으며, 이식성이 항상 최고의 가치인 것도 아닙니다. 위의 bind함수는 C99
외의 다른 약속(posix나 기타 널리 쓰이는 약속)이 어느정도의 이식성을 보장합니다.

다만, 애초에 bind 함수를 좀 더 잘 설계했더라면 C표준에 의해서도 이식성을 보장받을 수 있는
구조였겠지요. 성능면에서는 좀 손해를 볼지도 모르겠습니다만.

전웅의 이미지

오래전부터 나왔던 이야기지만 bind API 설계는 bad design 으로 악명이
높습니다. 개인적인 메일로도 종종 bind API 에 대한 문의가 오고는
합니다.

구조체간 멤버 vs. 멤버 변환 함수를 도입하거나 (아직도 이식성 문제를
안고는 있지만) 공용체를 도입하거나 해서 보다 설계적인 면을 보강할
필요가 있던 API 였습니다.

그래서 많은 사람들이 bind() 함수 호출 부분을 wrapping 해서 프로그램
내에서 한 곳에서만 이루어지도록 만들라고 추천하고 있습니다. 추후
이식성 문제가 발생할 경우에 유지보수를 보다 편하게 하기 위함입니다.

참고로, 저 위에 언급하는 C89 의 tag 이 달라도 layout 이 동일하면
호환된다는 이야기는 서로 분리된 t.u. 사이의 구조체에만 적용되는
이야기입니다. 그 부분만 딱 따오시는 바람에 그 규정이 모든 구조체형
사이에 적용된다고 생각하시면 오해입니다.

또한, 그 규정에서는 분명 멤버 대 멤버로 대응하는 멤버가 호환되는 type
을 가져야 한다고 요구하고 있습니다. sockaddr 과 sockaddr_in 은 결코
그런 구조를 가지고 있지 않습니다 - 따라서 서로 분리된 t.u. 에 놓는다고
해도 C89 의 요구조차 만족하지 못합니다.

또한, 서로 다른 tag 을 호환형으로 보장해주는 것은 C89 의 진정한 의도가
아니었습니다. 만약, 표준을 액면 그대로 받아들여야 한다면 서로 다른
구조체 포인터 사이의 변환은 C89 에서는 정의되지 않습니다 - 같은 표현,
같은 정렬제한 요구는 C99 에 추가된 것입니다 (자세한 내용은 아래 장문의
글에 담겨 있습니다).

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

전웅의 이미지

논의가 서로 다른 branch 로 흩어지고 있어 하나로 모아 답변합니다. 이
점 이해 부탁드립니다.

내용이 길어 스크롤의 압박이 상당합니다. 일부러 세부 주제별로 나눠
답변을 드렸습니다.

* 곁가지 문제

> 전웅님 답변에는 항상 곁가지가 있고, 그것은 이미 당연한 것으로 전제된 것이 많아서
> 때로는 논점이 흐려지거나, 어떤 면에서는 좀 답답한 부분도 있네요.
>

제가 곁가지를 그리 크게 치지 않았음에도 이미 논의는 다양한 논제를 담고
있군요. :-) 제가 님의 관심사가 아닌 부분에 대해 답하는 것은 주제를
흐리게 하고 답답한 일이며, 어느 순간부터 님의 관심사가 되어 질문-답변
이 오고 가면 그때부터는 집중해야 하는 주제가 되는 것인지요?

제가 누군가로부터 지원을 받으며 글을 쓰고 있는 것이 아닌 이상 제가
이곳에 글을 쓸 때에는 (게시판 원칙에 반하지 않는 이상) 제 가치관을
따라 쓸 수 있는 자유가 있습니다.

님께서는 제 답변 스타일이 곁가지가 많아 답답하다고 생각하실지 모르지만
저는 제 글에 가능한 기술적 오류를 덜 담으려 노력하는 것이고 더불어
혹시나 있을지 모르는 오해의 여지를 처음부터 막으려 하는 의도입니다.

제가 지나치게 뉴스그룹에 익숙해진 것이 원인일지 모르겠지만, 뉴스그룹
에서는 OP 의 관심사가 아닌 사항에 대해 논의가 이루어지는 일이 비일비재
하며 실제로 그로 인해 알지 못하던 부분에 대해 알게 되거나 의외의 문제
들을 발견하고 해결하는 일이 많습니다.

그와 같은 방식이 님에게는 답답할지 모르겠지만, 최소한 저에게는 공부를
하는데 있어 상당히 유용하다고 생각하고 있으며, 서로 다른 배경 지식을
갖는 분들이 이 논의를 따라오며 이해할 때 조금이라도 도움이 된다고
생각합니다.

"논의"가 아닌 사실상 "답변"을 하고 있는 상황에서 제 글 쓰는 스타일까지
(어떤한 방식으로든) 지적을 받아야 하는 일은 상당히 불쾌할 수 밖에
없습니다. 제가 최소한 외계어를 쓴 것은 아니지 않습니까? :-)

* 호환의 개념

> 이미 전웅님께서 모든 구조체의 포인터는 정렬 제한이 같다고 언급하셨고,
> 저는 이것을 '호환되는 타입' 이라는 의미로 받아들였습니다.
> 표준상에서는 분명히, 'same' 이라 하지 않고 'compatible' 이라 되어있습니다.
>
> 이 부분을 명확히 하실 필요가 있습니다. 받아들이기는 어렵습니다만,
> 전웅님 말씀에 의하면, 구조체 포인터들은 정렬 제한이 같으나 호환되는 타입은 아닌 것입니까?
>

같은 정렬제한, 같은 표현과 호환의 관계에 대해 물으셨는데, 님의 글에
대한 직접적 답변은 아니지만, 이미 다른 분의 질문에 답하면서 호환과
정렬제한/표현에 대한 관계를 분명히 밝힌 바 있습니다.

같은 정렬제한/표현은 호환형의 필수 조건이지만, 충분 조건이 아닙니다.
따라서 어떤 두 type 이 같은 정렬제한, 표현을 가지고 있다고 해도 둘은
호환형이 아닐 수 있습니다. 즉, 호환형은 type algebra 의 관점에서
보아야 합니다. 이미 char *, signed char *, unsigned char * 이야기를
통해서 예까지 든 것으로 기억하고 있습니다. 따라서 그 부분에 대해서
만큼은 더 이상 오해가 없었으면 합니다.

* 구조체형의 호환 문제

> 하지만, C89 표준상으로는 다음에 토론에 언급되는 것처럼,
>
> http://www.velocityreviews.com/forums/t318198-declaration-of-structs-ins...
>
> Two types have compatible type if their types are the same.
> Additional rules for determining whether two types are compatible are
> described in $3.5.2 for type specifiers, in $3.5.3 for type
> qualifiers, and in $3.5.4 for declarators. Moreover, two
> structure, union, or enumeration types declared in separate
> translation units are compatible if they have the same number of
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> members, the same member names, and compatible member types; for two
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
> structures, the members shall be in the same order; for two
> ^^^^^^^^^^
> enumerations, the members shall have the same values.
>
> 이렇습니다. 물론 C99 가 최신 표준이며, 이 부분에서 C89 와 C99 가 호환되지 않는 측면이겠지요. 이 토론에도 C99 에 왜 그런 내용이 추가되었는지 이해되지 않는다는 말이 있습니다.
>

기본적으로 두 구조체형은 멤버가 같은 type, 같은 이름, 같은 순서로 선언
되어 있더라도 서로 다른 type 입니다. 심지어는 동일한 translation unit
안에서 위의 조건을 만족하고 동일한 tag 으로 선언되는 구조체도 서로
다른 type 이 될 수 있습니다 - 알고 계시리라 생각하고 예는 들지
않겠습니다.

이와 같은 구조체 호환 규칙이 분리된 translation unit 으로 논의를
옮기면 다소 완화됩니다. C99 이전에는 서로 다른 t.u. 에 놓인 구조체의
경우 tag name 이 동일하지 않더라도 동일한 layout 만으로 호환형이라고
정의하고 있습니다. 그리고 C99 에 들어와 tag identitiy 를 요구하기
시작했습니다.

우선 드릴 말씀은 C89 에서 인용하신 부분은 두 개의 "분리된" t.u. 에만
해당되는 부분이기 때문에 하나의 t.u. 안에서 선언되었고 동일한 layout
과 서로 다른 tag 을 갖는 두 구조체에는 적용되지 않습니다. 따라서
분리된 t.u. 임을 언급하지 않고 보여주신 예는 "여전히" 잘못된 예입니다.

그렇다면 보여주신 예에서 (C89 환경을 가정하고) 두 구조체 중 하나를
다른 t.u. 로 옮기면 어떻게 될까요?

/* C89 가정 */
 
- a.c -
struct t1 { ... };
extern int func1(struct t1 *);
 
- b.c -
struct t2 { ... } foo;
extern int func1();    /* 일부러 비원형으로 */
int (*func2)(struct t2 *) = func1;
func2(&foo);    /* okay */

결과는 이렇습니다. 즉 인용하신 규칙은 이와 같은 경우에 적용될 수 있는
규칙입니다.

그렇다면 상당히 재미있는 논의로 이야기가 이어집니다. (상당히 골치 아플
수 있으므로 잠시 커피 한잔 하시고 오는 것도 좋습니다. ;-)

소스 A 안에 구조체 foo 와 bar 는 동일한 layout 을 가지고 있지만 서로
호환될 수 없는 type 입니다. 소스 B 안에서 구조체 foobar 는 구조체 foo
와 동일한 layout 을 가지고 있기 때문에 foo 와 호환되며, 동시에 bar 와
호환됩니다. 결론적으로 foo-bar 는 호환되지 않는데, foobar-foo,
foobar-bar 는 호환됩니다.

그렇다면 foobar-foo, foobar-bar 의 관계에 의해 foo-bar 도 호환형으로
보아야 할까요? 즉, 소스 A 안에서 struct foo * 를 struct bar * 로 변환
해 멤버에 접근하는 것이 정의되는 것일까요?

(한 t.u. 안에서 동일한 tag 을 갖는 서로 다른 구조체형을 선언할 수 있기
때문에 이 문제는 C99 에도 그대로 적용됩니다.)

이러한 논리하면 하나의 t.u. 안에서 서로 동일한 layout 을 갖는 두
구조체는 다른 t.u. 의 동일한 layout 의 구조체와 호환될 수도 있는
가능성 때문에 사실상 호환되는 구조체로 보아야 하지 않을까요?

이것이 바로 님이 진정 묻고자 하는 바라고 생각합니다.

대답은 "No" 입니다. 정말 비논리적이라고 생각하시겠지만, type
compatibility 는 transitive 하지 않습니다. 즉, A 와 B 가 호환되고,
B 와 C 가 호환되어도 A 와 C 가 호환된다는 법은 없습니다. 이쯤 오면
(과거의 제가 그랬듯이) 대부분의 분들이 현실적인 구현체를 생각하면서
"표준이 잘못되었다", "현실적으로 말이 되지 않는다", "논리적이지
못하다"는 등의 반응이 나옵니다. 하지만, 이것이 규칙입니다. 즉, foo 를
foobar 로 보내고 bar 를 foobar 로 보내는 것은 가능하지만, foo 를 bar
로 직접 보내는 것은 불가능하다는 뜻입니다. 그렇다면 어느 경우에
현실적인 문제가 일어날 수 있을까요?

세 구조체가 모두 동일한 표현 (즉, 내부적인 layout) 을 갖는다고 해도
최적화의 가능성 때문에 정의되지 않은 행동이 발현될 수 있습니다. 즉,
서로 호환되지 않는 type 의 대상체에 접근해 값을 읽거나 변경하는 등의
행위가 UB 이기 때문에 그 부분에 구현체는 최적화를 가할 수 있게 됩니다.
C 처럼 aliasing 이 빈번히 일어나는 언어에서 비호환형에 대한 접근을
금지하는 것은 상당히 유용한 최적화를 허락하는 길입니다. 즉, 위에서
struct foo 형으로 struct bar 형의 대상체를 접근하는 행위가 최적화의
영향을 받아 엉뚱한 결과로 나올 수 있다는 것입니다.

벌써 3번째 언급되고 있습니다만, common initial sequence 에 대한 특별한
규칙 중 하나가 바로 공용체에 포함된 서로 다른 구조체를 통해서 c.i.s 에
접근할 때는 반드시 해당 공용체 선언이 보여야 한다는 것입니다. 이는
구현체에게 layout 은 물론 최적화도 신중히 할 수 있는 충분한 정보를
제공하기 위함입니다.

이 정도면 처음 질문하셨던 코드에서 struct s1, s2 가 호환되지 않는 type
이라는 점은 분명히 밝혔다고 생각합니다. type 호환성에 대해 구구절절
적은 부분에 대해서는 의심하지 않으셔도 좋습니다. 잘 찾아보시면 유사한
문제를 놓고 3-4년 전에 csc 같은 뉴스그룹에서 제가 논의한 글도 찾아보실
수 있을 것입니다.

>
> 물론 C99 가 최신 표준이며, 이 부분에서 C89 와 C99 가 호환되지 않는 측면이겠지요. 이 토론에도 C99 에 왜 그런 내용이 추가되었는지 이해되지 않는다는 말이 있습니다.
>
...
>
> 주장하는 바는 C99 가 최신 표준이어도, 이 논의에 대한 실질적 표준(de facto)은 C89 가 맞다는 것입니다. C99 에서 가한 제약이 타당한 근거를 가질 수 없다면, 표준이 틀렸다고 볼 수 있다는 것입니다.
>
> 즉, C99 상으로는 호환 없는 타입일지모르나, C89 상으로는 호환 되는 타입이며, 이 둘 중 무엇이 옳으냐는 단지 C99 가 최신 표준이라는 이유로 결정될 수 없다는 것이구요.
>

이제 또 다른 세부 질문으로, 왜 그와 같은 tag identity 규정이 분리된
t.u. 사이의 구조체 호환에 C99 가 되어서야 요구되었으냐에 대한 대답은
이렇습니다.

원래는 C89 부터 요구했어야 합니다. 사실 분리된 t.u. 사이에서 호환되는
구조체를 선언하는 바람직한 방법은 tag 을 동일하게 선언하는 것이었고
C FAQs 는 물론, 기타 거의 모든 style 관련 자료, C 언어 입문서에서 이를
다루고 있었기 때문에, 오히려 논의 중에 "C89 에 따르면 tag 가 달라도
된다"는 사실을 언급했을 때 놀라는 사람이 더 많았습니다. 즉, 지극히
당연하고 또 바람직하다고 생각했던 사실이었기에 C99 에서 문제 없이 그와
같은 요구가 명문화될 수 있었습니다.

C89 는 경험이 부족한 상태로 탄생한 첫 표준이기 때문에 모든 세세한 요구
를 다 담지 못한 경우가 많습니다. 즉, "암시적 요구"를 제대로 수용하지
못한 셈입니다.

참고로 구조체 포인터가 같은 정렬제한, 같은 표현을 갖는다는 보장은 C89
에는 없습니다. 즉, 님의 해석대로 C89 표준을 "의도"가 아닌 있는 그대로
받아들여야 한다면 C89 에서는 아예 서로 다른 구조체 포인터 사이의 변환
시점부터 UB 가 됩니다. 구조체 포인터의 동일 표현/정렬제한 역시 C89
에는 명문화되어 있지 않지만 표준의 다른 요구들을 기반으로 논리적으로
유추할 수 있는 요구이기 때문에 C99 에 추가된 것이고, C89 에도 그대로
적용되는 것으로 보는 것입니다 - 이 논리적 유추 과정 역시 1-2년전 csc
에 포스팅한 제 글에 적었던 것으로 기억합니다.

> 구조체의 포인터를 호환성 있는 타입으로 간주하는 무수히 많은 코드들이 현존합니다.
>

표준 위원회에서 어떤 결정을 내릴 때에는 각 업체의 전문가들이 모여 기존
코드에 대한 영향을 최소화하는 범위 내에서 결론을 내리게 되어 있습니다.
그 과정에서 이 세상 모든 C 코드가 감안될 수 있는 것은 아니지만,
"보존될 가치가 있는 중요한" 코드는 모두 고려 대상이 됩니다. 표준
이전부터 존재하던 관례적인 약속을 모르고 혹은 "일부러 무시하고" 작성한
코드까지 표준이 아끼고 사랑해야 하는 것은 아닙니다. 이러한 코드는 많은
경우 어차피 자신의 목적한 바를 C 표준 안에서는 얻을 수 없기 때문에
(C 표준 관점에서의) 이식성이 포기하면서 의도적으로 표준을 어기는
경우가 적지 않습니다. 표준이 그렇게 잘못 설계된 코드까지 배려해 얻을
수 있는 다른 이익(최적화로 인한 성능, 다양한 구현체 수용, type safety
향상)을 포기할지의 문제는 위원회에서 결정하게 됩니다. 그리고 적지 않은
경우 그 결정은 옳습니다.

> 게다가 C89 에서 그것은 표준이었구요. '표준'이 '옳다'를 의미할 수는 없습니다. 수정되어야 하는 부분도 반드시 있는 것이구요.
>

표준이 수정될 수 있는 경우는, 실제 의도를 제대로 전달하지 못했거나
(즉, 오해의 여지가 크거나), 명백한 논리적 모순이 담겨 있거나, 개선이
이루어지는 경우 뿐입니다. C89 시절의 규칙으로 돌아가는 것이 이 3가지
중 하나에 해당될까요? type safety 보강을 위해 implicit declaration 과
implicit int 도 과감히 제거한 C99 입니다.

이 정도면 충분히 구조체 형호환 문제는 다루었다고 생각합니다 - 더 이상
이 부분을 이야기하다가는 책이 나올 지경입니다.

* 함수 포인터 형변환의 가치

> C 언어에서는 분명히 함수 포인터 캐스팅을 지원합니다. 하지만, 전웅님 말씀대로라면,
> 이 함수 포인터 캐스팅은 어디에도 쓸 곳이 없습니다. 결국 표준을 위배하도록 만드는
> 함수 포인터 캐스팅 문법은 왜 C 표준인지요?
>
...
> C99 표준에 따르면, 함수 포인터 캐스팅 문법을 사용하는 것은
> 항상 표준을 위배하게 될 것으로 예상되지만, 여전히 함수
> 포인터 캐스팅 문법 자체는 표준인 것에 대해 어떻게 생각하시는지요?
>

정수-포인터간 변환의 결과는 표준 관점에서 100% 정의되지 않은
행동입니다. 서로 다른 type 의 포인터 변환의 결과는 표준 관점에서 90%
(^^) 정의되지 않은 행동입니다. void * 형은 dereferencing 도 불가능
하면서 void * 와의 포인터 변환이 정의되어 있습니다. volatile 를
"유의미"하게 사용하는 프로그램은 표준 관점에서 95% (^^) 이식성을 갖지
못합니다.

그럼에도 불구하고 표준은 표준의 위반 가능성을 높이는 구조를 포함하고
있을까요?

표준은 세상 모든 프로그램이 표준의 테두리 안으로만 들어와야 한다고
생각하지 않기 때문입니다. 즉, 구체적 메카니즘은 이식성을 갖지 않지만
최소한 그런 메카니즘에 접근할 수 있는 공통된 표준적인 방법은 제공할
가치가 있다는 것입니다.

하지만, 함수 포인터 사이의 변환은 다른 분께서 설명하셨듯이 표준의 범위
내에서도 유용하게 쓰이는 경우가 많습니다. void * 가 대상체를 접근하지
않도고 이식성을 갖는 방법으로 유용하게 쓰이듯이 함수 포인터도 꼭
호환되지 않는 type 으로 변환해 "호출해야지만" 유용하게 쓰이는 것은
아닙니다. (애초에 void * 로 변환하지 않으면 된다고 말씀하셨는데
저로서는 도저히 이해하기가 힘들군요. void * 가 도입되기 이전부터 많은
코드가 void * 개념의 포인터를 사용하고 있었고 이를 반영해 void * 가
도입된 것입니다. 표준이 void * 를 도입했기 때문에 사람들이 그 전까지는
없던 새로운 방법을 사용하기 시작한 것이 아닙니다)

다른 type 의 함수 포인터로 변환했다가 다시 원래 type 으로 되돌렸을 때
정보가 보존된다는 것은, 어떠한 type 의 함수 포인터도 함수 포인터의
generic 포인터로 쓸 수 있다는 것을 의미합니다 - 참고로 void * 는 함수
포인터의 generic 포인터 역할을 하지 못합니다.

제 경험을 기억해보자면 table-driven 방식을 피해 FSM 을 구현할 때 다음
state 실행을 위한 함수에 가리키는 함수 포인터를 반환하는 함수를 구현할
때 표준이 보장하는 함수 포인터 변환의 특성을 유용하게 사용했습니다 -
나중에 보니 이 문제가 C FAQs 에도 언급되어 있더군요. 물론 그 코드는
함수 포인터와 관련해서는 표준이 정의하는 행동의 범주 안에 머뭅니다.

또한 현재 하고 있는 일의 코드에서도 table-driven 방식과
function-driven 방식의 중간쯤에 해당하는 방법으로 FSM 을 구현할 때도
표준이 보장해주는 특성이 쓰이고 있습니다.

전자는 전부를 순수하게 제가 작성한 것이며, 후자는 이름만 말하면 다들
알고계실 외국 업체에서 기본 틀을 작성해 받아 확장한 것입니다. 즉, 저만
특이하게 그런 방법에 의존하고 있는 것이 아닙니다.

자신에게 쓸 곳이 없어 보인다고 "어디에도 쓸 곳이 없다"라고 단정짓는
것은 다소 성급한 일반화가 아닌가 생각합니다.

* C/C++ 이야기

> 이것 조차 표준 위배라면, 캐스팅을 아예 쓰지 않아야 된다는 말씀 같은데요.
> 그렇다면, C++ 에서 부모 클래스의 메소드를 자식 클래스 인스턴스가 호출하는 것은
> C 표준상으로 어떤 상태라고 보시는지요?
...
> 이 부분에서 호환을 논하자면, C++ 의 계승 구조에 대해서는 어떻게 생각하지는지요?
>
[C++ 코드 생략]
>
> 단지 C 와 C++ 이 다른 언어이기 때문이다라고 생각하시는지요?
>
...
> C++ 을 예로 든 것은, C++ 의 계승 구조에 대한 내부적 형태는 결국 C89 에서 정의하는 호환성있는 구조체에 대한 언급과 동일하기 때문입니다.
>

C++ 는 C 언어와 "다른" 언어입니다. 여러 부분에서도 많은 차이를
보이지만, 큰 차이를 보이는 부분 중 하나가 type system 입니다. C 에는
존재하지 않는 구조를 새로운 규칙과 함께 추가한 것은 논외로 치더라도
C++ 가 (결과야 어찌되었건) C 언어보다 더 안전한 type system 을 추구
했기 때문에 C 언어보다 캐스트 연산이 빈번하게 일어나며 (즉, 프로그래머
가 그와 같은 변환을 실제로 의도한 것임을 캐스트를 통해 확인받는 것으로
생각할 수 있습니다), 그 의미가 잘 정의되어 있는 경우가 많습니다. C++
에서 일어날 수 있는 경우라 해서 동일한 경우도 아닌 유사한 경우가 C
언어에서도 일어날 수 있다고 가정할 수는 없습니다. 두 표준을 비교해
보시면 아시겠지만, 동일한 형태의 선언도 전혀 다른 개념과 방법을 통해
정의하고 있는 부분이 많습니다. 그로 인해 C 언어가 어설프게 C++ 의 특징
을 도입했다가 수습되지 않는 기괴한 문제를 만나기도 합니다 - const 로
한정된 다차원 배열과 포인터 사이의 문제가 이에 해당됩니다.

반면, C 언어는 "표준 관점에서" 올바른 프로그램은 강제 형변환을 *거의*
하지 않아도 되도록 많은 부분에서 배려를 하고 있습니다. 따라서 표준을
따르는 프로그램을 작성하는 과정에서 강제 형변환이 나왔다면 이는 이식성
에 손상을 줄 수 있는 행동을 할 가능성이 큰 부분으로 이해하는 것이
좋습니다.

이 문제를 논함에 있어서 C++ 을 끌고 오는 것은 아무런 의미가 없습니다.
그 부분이 궁금하시다면 언어 vs. 언어의 관점이 아니라 둘을 포용하는
프로그래밍 언어론적 관점에서 접근하시는 것이 더 낫다고 생각해 봅니다.

마지막으로, C/C++ 비교는 가능한 "유사점"이 아닌 "차이점"에 중심을 두는
것이 바람직 하다는 것이 제 개인적인 생각입니다. 비록 C/C++ 위원회 모두
가 그 차이를 좁히는 것을 목표 중 하나로 하고 있다지만, 개인적인 바람
으로는 맘에 들지 않는 C 언어를 포함하려는 욕심으로 인해 더 맘에 안들어
버린 C++ 가 빨리 분리되어 나가기를 바랍니다.

이번주는 야근이 많아 주말에는 좀 느긋하게 쉬려 했는데 퇴근해서까지
컴퓨터 앞에 앉아있으려니 생각보다 지치는군요.. 휴...

점점 커지는 일교차에 감기 조심하시기 바랍니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

저도 함수 포인터 캐스팅을 자주 쓰고 있기 때문에 이 스레드에
집착하는 것입니다.

피로로 인해 착각하거나, 성급히 포스팅한 문제가 있지만,
아무튼 이 스레드 내용은 중요하면서, 또 앞으로 해 나가야 할
코딩을 피곤하게 만드는 부분이기도 합니다.

C89 인용 부분은 제가 한 것이 아니고, 링크된 토론 중에 있는
내용입니다. 그렇다면 저 분이 전체 인용을 하지 못한 것인데, 빠진
부분의 원문을 확인할 수 있는지요?

아무튼 요지는 알겠습니다. 코드를 수정할지는 좀 생각해봐야 되겠습니다.

---------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

> 저도 함수 포인터 캐스팅을 자주 쓰고 있기 때문에 이 스레드에
> 집착하는 것입니다.
>

예, 저 역시 요즘 임베디드 분야의 일을 하고 있는데 함수 포인터와 포인터
변환이 난무하는 코드 안에서 헤매고 있습니다. 작업을 하실 때 코드의
모든 부분이 반드시 표준 안에서 동작해야 한다고 집착하실 필요는 없을
것 같습니다. 저 역시 때로는 성능을 위해 혹은 선배 눈치를 보느라 표준을
어기는 코드를 사용하고 있습니다. 중요한 것은 그와 같은 코드가 표준을
따르지 않는다는 사실을 인지하는 것이라고 생각합니다 - 문서나 주석을
통해 표시해 놓는 것 만으로도 이식성 문제가 발생했을 때 유지보수를 도울
수 있다고 생각합니다.

> 피로로 인해 착각하거나, 성급히 포스팅한 문제가 있지만,
> 아무튼 이 스레드 내용은 중요하면서, 또 앞으로 해 나가야 할
> 코딩을 피곤하게 만드는 부분이기도 합니다.
>

동의합니다.

> C89 인용 부분은 제가 한 것이 아니고, 링크된 토론 중에 있는
> 내용입니다. 그렇다면 저 분이 전체 인용을 하지 못한 것인데, 빠진
> 부분의 원문을 확인할 수 있는지요?
>

빠진 부분은 C99 표준에서도 큰 차이가 없습니다. C89 표준을 지금 가지고
있지 않아 C99 표준에서 인용합니다. (참고로 Dan Pop 이 인용한 표준은
진짜 C89 입니다. 즉, ANSI 표준이라 clause 번호가 ISO 와 다릅니다. ANSI
표준의 clause 3 가 ISO 의 clause 6 에 대응합니다.)

Quote:

[두 타입이 compatible 하기 위해서 반드시 same 할 필요는 없다는
이야기가 나옵니다. 그리고 이어서] Moreover, two structure, union, or
enumerated types declared in *separate* translation units are
compatible if their tags and members satisfy the following
requirements:

그리고나서 인용하신 부분이 이어질 겁니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

전웅의 이미지

잉?? 그러고 다시 보니 Dan Pop 이 인용한 부분에도
"separate translation unit" 부분이 들어가 있습니다.

즉, 그 이후 이어지는 부분은 "분리된" translation unit (익숙한
개념으로 옮기면 "소스 파일")에 적용된다는 이야기입니다.

C++ 은 one definition rule 이라는 것을 가지고 있지만, C 는
최대로 큰 scope 이 file scope 이기 때문에 인용한 것과 같은
부가적 규칙이 필요합니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

예 나오는군요.
그럼 Dan 이라는 분이 인용하면서 잘 못 이해한 것 같네요. 그 윗 분 토론자하고.

--------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

익명사용자의 이미지

C99보다는 de facto를 더 중시하시는 분 같은데 굳이 표준에 집착하시는 이유는 뭔가요? 그냥 하시던 대로 하시면 될 듯... 위에 쓴 글들도 뭔가 새로운 것을 알기 위해서라기 보단 C99가 틀렸다고 주장하면서까지 자신이 하던 방식이 옳다는걸 우기기 위함이 아니었습니까. 지금까지도 별 문제없이 지내셨던 걸로 봐서 그렇게 극도의 이식성이 꼭 필요하신 분도 아닌거 같은데... 님의 방식은 충분히 옳으니까 지금까지 하셨던 대로 de facto대로 짜십시오.

체스맨의 이미지

저를 잘 아시지 못하면서, 100배는 미리 넘겨짚으시는 것 같군요.
솔직히 넘겨짚기는 꽤 좋은 느낌은 아니네요.

단지, 소스를 고치는게 쉬운 일이 아니기 때문이라는 것은
개발자라면 잘 아실 겁니다. 그리고 반대편 주장에 의해 더 좋은
사실도 논의 되잖습니까?

---------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

> 단지, 소스를 고치는게 쉬운 일이 아니기 때문이라는 것은
개발자라면 잘 아실 겁니다.
>

ㅋㅋㅋ 100% 동"감"합니다.

제가 짠걸 고칠 때도 끔찍한데 하물며
남이 짜 놓은 걸 고쳐야 할 때는... --;;;;;

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

아무튼 토론중 신경이 날카로워졌던 것은 피곤한 상태에서
머리를 굴리려니 힘에 부쳐 그런 것이니 이해해주시고,
이 스레드와 별도로 묻고 싶은 내용들이 있는데, 전웅님 개인
메일을 이용해도 괜찮을런지요. 답변은 편하실때 주시면 됩니다.
두세번 정도만 보내겠습니다.

표준 문제는 실무 능력과 별개로 항상 골치아프고 어렵습니다.

-----------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

넵, 답변을 천천히 드려도 괜찮으시다면 얼마든지 개인 메일로
연락 주셔도 좋습니다. 회사 업무에 치여 요즘은 쉬는 시간에
가능한 컴퓨터를 멀리 하려 하고 있습니다. ^^

아무래도 이 분야가 저한테 맞는 분야는 아닌것 같습니다. --;

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

주제와 약간 벗어나는 감이 있지만, 전웅님 개인 메일을 이용하는 대신
질문을 하나 드릴까 합니다.

offsetof 관련 논의는 전에 있었던 것 같고, offsetof 가 일단 표준에 부합한다는 전제하에,
다음과 같은 코드는 표준에 부합한다고 보는데 어떠신지요?

struct _s1, _s2, _s3 는 서로 다른 구조체 이고,

struct _s1 {
	int a;
	struct _s2* b;
}* s1;
 
// _s1 의 b 멤버에, 옵셋과 _s2 대신 _s3 로 캐스팅하여, NULL 을 대입한다.
*(struct _s3**)( (char*)s1 + offsetof(struct _s1,b) ) = NULL;

그리고 다음,

union {
    struct _s1* s1;
    struct _s3* s3;
} var;
 
struct _s1 s1;
var.s3 = (struct _s3*)&s1;
var.s1->a = 1;

대충 뭘 고민하고 있는지 아시리라 생각합니다.
맞는 것 같아도, 소스 수정의 대란을 몇 번 겪으니 아주 조심스럽군요...

----------------------------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

> 주제와 약간 벗어나는 감이 있지만, 전웅님 개인 메일을 이용하는 대신
> 질문을 하나 드릴까 합니다.
>
> offsetof 관련 논의는 전에 있었던 것 같고, offsetof 가 일단 표준에 부합한다는 전제하에,

넵, offsetof 의 구현 방법은 표준에 부합하지 않을 수 있어도, offsetof
의 사용은 표준에 부합합니다 - 구현체는 어떻게 해서든지 offsetof 의
행동이 표준에 맞도록 구현해 줍니다. 사용하는 입장에서는 offsetof 자체
의 구현에 대해 고민할 필요가 없습니다.

> 다음과 같은 코드는 표준에 부합한다고 보는데 어떠신지요?
>

struct _s1, _s2, _s3 는 서로 다른 구조체 이고,
 
struct _s1 {
int a;
struct _s2* b;
}* s1;
 
// _s1 의 b 멤버에, 옵셋과 _s2 대신 _s3 로 캐스팅하여, NULL 을 대입한다. 
*(struct _s3**)( (char*)s1 + offsetof(struct _s1,b) ) = NULL;

예 여기까지는 아무 문제 없습니다.

> 그리고 다음,
>
>

union {
    struct _s1* s1;
    struct _s3* s3;
} var; 

공용체로 호환되지 않는 두 포인터를 묶어주는 것이 포인터가 가리키는
대상에 대해 특별한 무엇인가를 보장해 주지는 않습니다. (공용체로 common
initial sequence 를 갖는 두 구조체를 묶어주는 것은 특별한 의미를
갖습니다.)

struct _s1 s1;
var.s3 = (struct _s3*)&s1;

여기까지는 구조체 포인터의 정렬제한/표현에 대한 요구로 인해 아무 문제
없습니다.

var.s1->a = 1;

구현체가 공용체를 다루는 모습을 고려했을 때 현실적으로 가시적인 문제
를 일으킬 가능성은 0 에 가깝습니다. 하지만, 표준과 관련된 답변을
원하실테니 가장 엄격한 해석을 말씀드리겠습니다.

이 경우 var.s3 에 저장된 값 자체는 struct _s1 * 이었지만, 일단 s3 로
저장되고 난 이후에는 var.s3 의 값이 됩니다. 이때 var.s1 에 접근하게
되면 이는 struct _s3 * type 의 대상체(object)에 존재하는 값을 struct
_s1 * 형으로 접근 (type punning) 하는 결과가 됩니다. 이는 허락 되지
않은 행동입니다.

표준에는 lvalue access rule 이라고 부르는 규칙이 있습니다. 이는 특정
type 의 object 에 접근할 수 있는 type 을 제한하는 규칙으로 전형적인
예로 int 형의 변수를 float 형으로 접근하는 것 등을 생각할 수 있습니다.
이 규칙의 목적 역시 바람직하지 않은 접근을 UB 로 규정함으로써 컴파일러
의 최적화를 용이하게 하는 것입니다.

void func(float *f, int *i, unsigned char *uc)
{
    *f = 3.14;
    *i = 3;
    func2(*f);    /* can be optimized to be func2(3.14)! */
    *uc = 0;
    func2(*f);    /* cannot be optimized to func2(3.14)! */
}
 
...
float f;
func(&f, (int *)&f /* UB */, (unsigned char *)&f);

즉, "대부분"의 경우 공용체는 여러 type 을 가진 멤버 중 가장 마지막에
저장한 멤버를 통해서만 값을 읽어야 합니다 - 위에서 보인 것(uc)처럼
lvalue access rule 이 허용하는 경우나 common initial sequence 규칙에서
허용하는 경우에는 그렇게 하지 않을 수도 있습니다. 즉, 위에서 다룬 수정
된 dlsym() 함수의 예가 표준이 보장하는 공용체의 전형적인 사용 형태라
생각할 수 있습니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

체스맨의 이미지

좋은 답변 감사드립니다.

-----------------------
Coral Library Project : http://coral.kldp.net
Orion Project : http://home.megapass.net/~heesc22/

Orion Project : http://orionids.org

전웅의 이미지

방금 체스맨님께서 메일로 주신 내용에 대한 답장을 쓰다가 갑자기 제가
잘못 답변한 부분이 있다는 걸 깨닫게 되어 정정하기 위해 글 남깁니다.

최종적인 답변(특정 코드가 표준에 부합하느냐 그렇지 않느냐에 대한 답변)
에는 변화가 없습니다. 다만, 그 설명 과정에 잘못 설명한 부분을 정정
합니다.

이 내용은 제가 작년 가을에도 헷갈리는 바람에 고생했던 내용인데 안
써먹다보니 또 다시 같은 실수를 반복하네요. --;;;;

일단 내용에 대한 설명 드리고 제가 답변 드린 부분 중에서 잘못된 내용에
대해 다시 답 드리겠습니다.

----------------------------------------------------------------------
두 구조체 포인터가 같은 정렬제한을 갖는다는 것은 말 그대로 "포인터"가
같은 정렬제한을 갖는다는 뜻입니다. 즉, 어떤 데이터형 T1 과 T2 가 같은
정렬제한을 가지면,

    T2 t2;
    &t2 == (T2 *)(T1 *)&t2;

가 보장됨을 의미합니다. 따라서 두 구조체 포인터가 같은 정렬제한을 갖는
다는 것은

    struct foo *pfoo;
    struct bar *pbar; 
    &pfoo == (struct foo **)(struct bar **)&pfoo;

임을 의미하는 것이지

    pfoo == (struct foo *)(struct bar *)pfoo;

를 의미하는 것이 아닙니다. 따라서 "여전히" 두 구조체 포인터 사이의
변환은 호환 type 이 아닌 경우 정렬제한과 관련된 문제를 겪을 수 있고
표준의 관점에서 그 행동은 undefined behavior 에 해당합니다.

    pbar = (struct bar *)pfoo;    /* UB */

따라서 이 (올바른) 해석을 적용하면 더 일찍 답이 끝날 내용들이 많은데
보장되지 않는 내용이 보장된다고 잘못 생각하는 바람에 답변이 길어진
부분이 많습니다.

따라서 답변을 읽으실 때 논리적으로 두 구조체 포인터 사이의 변환이 문제
없다는 것을 가정하고 있는 부분은 특정 구현체가 (실제로는 대부분의
구현체가 보장해주지만) 그런 사실을 보장해주더라도 ... 된다는 식으로
받아들이시면 될 것 같습니다.

(분명히 표준은 두 구조체 포인터 사이의 변환을 정의해 주지 않지만,
대부분의 구현체가 서로 다른 type 의 구조체에도 같은 정렬제한을 적용
하기 때문에 두 구조체 포인터 사이의 변환 자체는 큰 문제를 일으키지
않는 것이 현실입니다.)

이제 구체적인 답변 중 잘못된 부분 정정하겠습니다.

----------------------------------------------------------------------

>
> type A 를 struct s1 *, type B 를 struct s2 * 로 놓으면 type A 에서
> type B 로의 변환은 항상 정보 손실 없이 이루어집니다
>

사실이 아닙니다. 위에서 보인 것처럼 정보 손실이 있을 수 있습니다.

이 부분은 제가 정보 손실 없이 이루어진다는 사실을 근거로 사용한 부분이기
때문에 그 근거가 거짓이 되면 그 이후의 논리도 깨집니다. 하지만, 논리상
두 구조체 포인터 사이의 변환에 문제가 없는 경우를 가정하고 논리를 이끌어
가더라도 무리가 없는 부분입니다.

...

>
> 우선 다음 내용이 전웅님께서 주장하시는바가 맞는지 확인바랍니다. 이것이 주장하시는 바가 아니라 완전한 사실이라면 토론할 필요는 없습니다. 사실을 가지고 토론하는 것은 말이 안되지요.
>
> > 구조체끼리는 정렬 제한이 같아서 서로 캐스팅할 수 있고 이것은 표준에 위배되지 않는다.
> >
>

위배됩니다.

...

>
> - 구조체 포인터의 정렬제한과 표현은 같습니다. 따라서 구조체 포인터의
> 변환 자체는 문제를 일으키지 않습니다.
>

이미 보여드렸듯이 문제를 일으킬 수 있습니다.

...

> > struct _s1, _s2, _s3 는 서로 다른 구조체 이고,
> >
> > struct _s1 {
> > int a;
> > struct _s2* b;
> > }* s1;
> >
> > // _s1 의 b 멤버에, 옵셋과 _s2 대신 _s3 로 캐스팅하여, NULL 을 대입한다.
> > *(struct _s3**)( (char*)s1 + offsetof(struct _s1,b) ) = NULL;
> >
>
> 예 여기까지는 아무 문제 없습니다.
>

일단 (char *)s1 + offsetof(...) 가 가리키게 되는 곳은 struct s2 ** 로
이를 (struct _s3 **) 로 변환하는 것은 구조체 포인터의 정렬제한 보장
으로 문제가 없습니다. 하지만, 문제는 간접 지정 연산 * 에서 일어납니다.
이는 struct _s2 * 형의 대상체를 호환되지 않는 type 인 struct _s1 *
형으로 접근하는 것으로 이는 그 아래 부분에 설명드린 lvalue access rule
위반에 해당됩니다.

...

>
> struct _s1 s1;
> var.s3 = (struct _s3*)&s1;
>
> 여기까지는 구조체 포인터의 정렬제한/표현에 대한 요구로 인해 아무 문제
> 없습니다.
>

그렇지 않습니다. 저 변환도 문제가 될 수 있습니다. 따라서 그 아래에
나온 답변은 저 변환이 문제가 없는 경우를 가정하고 이루어진 답변으로
이해할 수 있습니다.

----------------------------------------------------------------------

늘 답변 드리면서 기술적으로 틀린 내용을 담지 않으려고 노력하고 있는데
저도 사람이라 어쩔 수 없나 봅니다.

죄송하다는 말씀으로 글 마칩니다.

p.s. 9월 18일 질문 메일 받고 설명 다소 수정하였습니다

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

>>두 구조체 포인터가 같은 정렬제한을 갖는다는 것은 말 그대로 "포인터"가
>>같은 정렬제한을 갖는다는 뜻입니다. 즉, 어떤 데이터형 T1 과 T2 가 같은
>>정렬제한을 가지면,
>>
>> T2 t2;
>> &t2 == (T2 *)(T1 *)&t2;
>>
/* 예제 1 */ 이라 칭합니다.

>>가 보장됨을 의미합니다. 따라서 두 구조체 포인터가 같은 정렬제한을 갖는
>>다는 것은
>>
>> struct foo *pfoo;
>> struct bar *pbar;
>> &pfoo == (struct foo **)(struct bar **)&pfoo;
>>
/* 예제 2 */ 이라 칭합니다.

>>임을 의미하는 것이지
>>
>> pfoo == (struct foo *)(struct bar *)pfoo;
>>
/* 예제 3 */ 이라 칭합니다.

>>를 의미하는 것이 아닙니다. 따라서 "여전히" 두 구조체 포인터 사이의
>>변환은

여기서 예제 1과 예제 3은 같다(같은 정렬제한을 갖는다면)라고 보여지는데, 이부분이 다르다면
설명좀 부탁드립니다.

예제 1과 예제 3이 같다는 전제 아래 결론부분이 바로 나와서 혼동이 됩니다.

-- 덧붙이는 글 --
여기서는 정렬문제로만을 이야기합니다.

전웅의 이미지

>
> /* 예제 1 */
>
> T2 t2;
> &t2 == (T2 *)(T1 *)&t2;
>
>
> /* 예제 2 */
>
> struct foo *pfoo;
> struct bar *pbar;
> &pfoo == (struct foo **)(struct bar **)&pfoo;
>
>
> /* 예제 3 */
>
> pfoo == (struct foo *)(struct bar *)pfoo;
>
>
> 여기서 예제 1과 예제 3은 같다(같은 정렬제한을 갖는다면)라고 보여지는데, 이부분이 다르다면
> 설명좀 부탁드립니다.
>

아, 예제 1과 3이 다르다는 뜻이 아니라, 표준은 T1, T2 가 "구조체
포인터"일 때(예제 2)를 보장해 준다는 것이지 "구조체"일 때(예제 3)를
보장해 준다는 것이 아니라는 뜻입니다.

이 부분이 틀리지 않았다면 오히려 쉽게 끝날 수 있는 답변이었기에 다른
최종 답변에 차이는 없습니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

>>아, 예제 1과 3이 다르다는 뜻이 아니라, 표준은 T1, T2 가 "구조체
>>포인터"일 때(예제 2)를 보장해 준다는 것이지 "

아래의 변환 관련 부분(6.3.2.3 Pointers)말고 달리 해석되는 부분이 있으면 알려주시면 이해하는데 도움이 될 것 같습니다.

Quote:

A pointer to an object or incomplete type may be converted to a pointer to a different
object or incomplete type. If the resulting pointer is not correctly aligned for the
pointed-to type, the behavior is undefined. Otherwise, when converted back again, the
result shall compare equal to the original pointer.

-- 추가된 글 --
struct type이 object 또는 incomplete type의 분류에 포함될 것라 생각하기에 위 내용을 인용하였습니다.
type들은 object types, function types, incomplete types 분류에 포함되기 때문이죠.

전웅의 이미지

6.2.5p26:

All pointers to structure types shall have the same representation
and alignment requirements as each other. All pointers to union types
shall have the same representation and alignment requirements as each
other.

따라서, 위에 인용해 주신 부분의 "Otherwise" 부분에 해당됩니다.

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

익명사용자의 이미지

All pointers to structure types shall have the same representation and alignment requirements as each other.

이 문장을 해석하는 방법에 대한 질문입니다.

C99 6.2.5Types p26의 한 부분입니다.

A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.39)

전웅님의 해석방식대로라면 위의 문장은

void * pv;
char * pc;
pc = (char *)(void *)pv;

를 뜻하는게 아니라

void ** pv;
char ** pc;
pc = (char **)(void **)pv;

를 뜻하는게 되는데요?

저는 표준이 구조체포인터끼리의 상호변환을 허용하고 있다고 생각합니다만... 상식적으로 생각해봐도 그렇고 문장 자체를 그대로 해석해봐도 그렇고 전웅님의 해석은 납득이 안갑니다.

전웅의 이미지

> All pointers to structure types shall have the same representation and alignment requirements as each other.
>
> 이 문장을 해석하는 방법에 대한 질문입니다.
>
> C99 6.2.5Types p26의 한 부분입니다.
>
> A pointer to void shall have the same representation and alignment requirements as a
> pointer to a character type.39)
>
> 전웅님의 해석방식대로라면 위의 문장은
>
> void * pv;
> char * pc;
> pc = (char *)(void *)pv;
>
> 를 뜻하는게 아니라
>
> void ** pv;
> char ** pc;
> pc = (char **)(void **)pv;
>
> 를 뜻하는게 되는데요?
>

넵, 맞습니다.

많은 오해 중 하나가 해당 문장을 잘못 해석하는 것입니다.

object_type * -> void * -> object_type * 이후 변환 전후 동일함
object_type * -> char * -> object_type * 이후 변환 전후 동일함

과 같은 특성은 인용하신 부분이 아니어도 표준의 다른 부분에 의해 보장
되는 사실입니다.

어떤 두 type 이 동일 정렬제한(과 동일 표현)을 갖는다는 것은 사실상 두
type 이 type algebra 관점에서는 구분된 type 이지만, 거의 동일한 type
으로 다루어짐을 명시하기 위한 것입니다. 물론, 그렇다고 두 type 을
맘대로 섞어 사용할 수 있는 것은 아닙니다만...

> 저는 표준이 구조체포인터끼리의 상호변환을 허용하고 있다고 생각합니다만...

그렇지 않습니다. 구조체 포인터가 가리키는 구조체의 정렬제한은 서로
다른 type 은 다를 수 있습니다.

구조체 "포인터"의 동일 정렬 제한이 요구된 이유는 그것이 구조체의
특별한 특성을 만족해주기 위함이 아닙니다. 말씀드렸듯이 C 언어가 지원
하는 opaque type 구현을 위해서는 반드시 가정되어야만 하는 시실을
뒤늦게 명시한 것입니다.

> 상식적으로 생각해봐도 그렇고 문장 자체를 그대로 해석해봐도 그렇고 전웅님의 해석은 납득이 안갑니다.
>

표준은 "논리"로 해석해야지 "상식"으로 해석해서는 곤란한 경우를
겪을 수 있습니다. ;-)

--
Jun, Woong (woong at icu.ac.kr)
Web: http://www.woong.org

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lovewar의 이미지

>>All pointers to structure types shall have the same representation
>>and alignment requirements as each other. All pointers to union types
>>shall have the same representation and alignment requirements as each
>>other.
>>
>>따라서, 위에 인용해 주신 부분의 "Otherwise" 부분에 해당됩니다.

인용해 주신 부분은 Type(struct에 대한 pointer)을 설계할 때 어떻게 해야 한다는 강제 조건을 준것이라 이해합니다.

-- 덧붙이는 글--
인용했던 내용(Conversion)에 인용해 주신 내용(Type)이 포함되는 것 같습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.