포인터의 위치에 따른 해석차이
글쓴이: Geniys / 작성시간: 목, 2005/10/13 - 9:17오후
#include <stdio.h> #include <stdlib.h> typedef struct a *APTR; typedef struct b *BPTR; struct a{ int afield; struct b *bpointer; }; struct b{ int afield; struct a *apointer; }; int main(int argc, char *argv[]) { BPTR b; b = (struct b*) malloc(sizeof(BPTR)); b->afield = 10; printf("%d\n", b->afield); return 0; }
C Programming FAQs에 있는 내용중의 하나입니다.b = (struct b*) malloc(sizeof(BPTR));
위의 코드와 같으면 에러가 발생하지 않지만 (struct *b) 포인터를 앞에 붙이면 에러가 발생합니다.
컴파일러가 어떻게 해석하기에 저런차이가 발생하는지 궁금합니다.
저는 intel C++ 컴파일러를 사용했습니다.
그리고 위의 소스에서 <struct a *apointer>에서 apointer를 사용하려면 malloc을 해줘야 하는지도 궁금합니다.
Forums:
...
코드가 조금 이상해보이는건 저 뿐인가요;;
typedef 된것을 풀어서 써보면 이렇겠죠?
struct b *b;
b = (struct b *)malloc( sizeof(struct b *) );
할당하도록 요청한 struct b * 는 항상 4바이트입니다. 만일 struct b 가 4바이트보다 크다면, b 를 사용할때 런타임 에러가 날 것으로 예상됩니다.
따라서 올바른 코드는..
또는
되겠습니다.
Re: 포인터의 위치에 따른 해석차이
아. 제가 질문을 잘못 이해했군요. 위에 제가 설명한것도 이 코드에 잠재된 문제입니다만.. 질문하신 내용은 그게 아니네요.
우선 첫째.
b = (struct *b) malloc(sizeof(BPTR));
이 코드는 문법적 오류입니다. 왜냐하면.. struct b 에서 b 는 구조체의 이름을 지칭하는 테그일 뿐이고, 그 자체로 메모리 어딘가에 위치해있다거나 하는 실제적인 객체가 아니기 때문입니다. (struct *b) 에서 b 는 BPTR b; 로 선언한 변수 b 가 아니라, 구조체 테그일 뿐입니다. struct *b 라는 구조체는 정의된 적이 없으니, 이 형변환은 오류입니다. 하지만..
b = (struct b *) malloc(sizeof(BPTR));
이건 문법상 적법합니다. 왜냐먼, malloc() 함수는 주소값을 리턴하는데, 이 주소가 struct b 형태의 주소다 라고 명시적으로 형변환을 해주는 상황이기 때문이죠.다음 둘째. 구조체 안에 포함된 포인터 apointer 를 사용하려면 어떻게 malloc() 해줘야 하는가.. 하는 점은, 2가지가 있겠습니다.
1) 직접 malloc() 해주는 방법
a->apointer = (struct a *)malloc( sizeof(struct a) );
이렇게 해주면 되겠죠? 그러면 또 그 안에 있는 apointer, 즉 a->apointer->apointer 를 어떻게 쓰느냐 하는 문제에 봉착하게 됩니다. 이 역시 malloc() 해주면 되지만, 그러면 또 그 안에 있는 apointer 가 생겨버리죠. a->apointer->apointer->apointer->apointer->.... 이렇게 무한히 계속 반복됩니다.
2) 다른 주소를 대입하는 방법
apointer 는 포인터이고, 그 자체로 4바이트의 크기를 갖고 있으므로, 4바이트 이내의 주소값은 뭐든지 대입할 수 있습니다. 따라서..
이렇게 서로가 서로를 참조하는 구조를 만들어낼 수 있습니다. 바로 이 방법이 C 에서 연결 리스트를 구현하는 원리입니다.
....
하나에 쓸걸, 자꾸 글이 길어지네요.;;
하나 더 참고로 말씀드리면.. 위에 님께서 올려주신 코드는 좋지 못한 경우의 표본이라 할 수 있습니다. 왜냐면, 지금 님이 햇갈려하시는 이유는 b 라는 기호가 구조체 테그로도 사용되고, 변수 명으로도 사용되기 때문에 무척 해갈리거든요. 님이 문의하신 첫번째 질문의 경우.. 님은 (struct b*)malloc( ... ) 에서 b 가 변수 명으로 사용된 것이라고 착각하신 경우입니다.
그리고 또 한가지.. 포인터 연산자 * 는, 반드시 공백과 함께 사용하는 것이 인식하기 좋습니다.
Re: 포인터의 위치에 따른 해석차이
정말로 C언어를 공부하시는 것이면 C언어에서 malloc을 쓸 때는 casting하지 마세요.
- 죠커's blog / HanIRC:#CN
Re: 포인터의 위치에 따른 해석차이
왜 그런가요? 함수의 정의를 보면 malloc() 함수는 void * 형을 리턴합니다. 이는 가용한 데이터형으로 캐스팅하는것이 올바르게 보이는데요. 실제로 캐스팅을 생략할 경우, 어떤 컴파일러에서는 데이터형이 맞지 않는다는 Warning 을 냅니다.
[quote]왜 그런가요? 함수의 정의를 보면 malloc() 함수는
일단, C에서는 void*형으로부터 다른 포인터 형으로의 암시적인 형변환이 지원이 됩니다. C++에서는 안되죠. 어떤 컴파일러에서 문제가 생긴다면 그것은 제대로 된 C 컴파일러가 아니거나 C++로 컴파일했기 때문입니다.
저는 이걸 스타일 상의 문제라고 봅니다.
StructA * a = (StructA*)malloc(sizeof(StructA));
이것보다는
StructA * a = malloc(sizeof(*a));
이쪽이 길이도 짧고, 만약 StructA의 이름이 변하게 될때 고쳐써야 되는 부분도 줄어들게 되겠지요.
하지만, 그밖에 반드시 casting을 생략해야 되는 이유는 찾지 못했습니다.
C++에서 malloc를 사용한다는게 웃기는 일이긴 합니다만, 대부분의 사람들이 C인지 C++인지도 제대로 구분 못한다는걸 생각해 볼 때, casting을 붙이는 것도 나름대로 의미가 있습니다. 최소한 C++ 컴파일러로 C코드를 컴파일 시켜놓고 경고 뜬다고 태클 들어오는 일은 없겠지요.
[quote="Anonymous"][quote]왜 그런가요? 함수의
이런 방법도 있겠죠. C 코드를 C++로 컴파일할 만한 사람이 생긴다면 적어도 이런 설명 정도는 적어 주는 게 낫지 않나 싶습니다.
- 토끼군
Re: 포인터의 위치에 따른 해석차이
C언어에서는 void *형으로 되어 있는 것을 캐스팅 안해주는 것이 표준에 맞습니다.
첫째로 void *의 캐스팅이 보장됩니다. 암시적인 캐스팅이 보장이 되는 코드에 명시적 캐스팅을 할 이유는 없습니다.
둘째로 impiclit declaration에서 안전한 코드가 됩니다. (적어도 C95까지는 implicit declaration이 적법한 코드이고 실제로 쓰이고 있습니다.) 혹시나 표준 해더를 include 하지 않았을 경우 int malloc()형을 요구하게 되고 컴파일러는 int형을 받아서 원하는 포인터 형(char * 등)으로 변환할려고 합니다. 만약에 명시적 캐스팅을 하지 않았다면 에러가 나서 이런 코드를 막아줍니다. 포인터 형을 정수형으로 바꿔주는 암시적 캐스팅은 없기 때문입니다.
셋째로 함수 포인터에 대입에 대한 에러체크가 되지 않습니다. malloc이 반환하는 자료를 함수 포인터로 캐스팅해서 받는다면 undefined behaviour를 초래할 수 밖에 없지만 프로그래머가 직접 "캐스팅"을 해주었을 때는 컴파일러는 아무말을 하지 않습니다.
그 외의 많은 상황에서 void *를 캐스팅해주지 말라고 C표준은 요구하고 있습니다. C++의 경우에는 캐스팅을 해주는 것이 맞습니다. 함수 내에서 사용된
코드가 C++ 98과 C 99가 전혀 다른 의미를 가지고 있는 것 처럼 C++과 C는 Syntax는 비슷하되 Semantic이 다른 언어가 되어 있습니다. 아마도 C++ 0x가 나오면 C++ 표준이 공식적으로 C90 표준을 지원하게 됨에 따라 차이가 줄어들 수 도 있겠습니다만... C90 코드의 호완성에 비중을 맞추는 것보다는 C++이 가지고 있는 일부 뒤처진 개념을 업데이트하는데 비중을 맞출 것 같습니다. (솔직히 이 부분보다는 type_info와 같이 말도 안되는 표준을 고쳐나가는 것이 더 급선무라고 생각합니다만...)
- 죠커's blog / HanIRC:#CN
[quote="dotri"]하나에 쓸걸, 자꾸 글이 길어지네요.;;하
좋은 예까지 들어주시다니.. 고맙습니다.
저 역시 코드를 읽었는데 나쁜 예로 인식 해서
헷갈리더군요..^^;;;
전 좋은 예 1과 같이 쓰는데,
2가 훨씬 괜찮아 보이는 듯.....
c'est un des orgueils de notre pauvre humanit?, que chaque homme se croie plus malheureux qu'un autre malheureux qui pleure et qui g?mit ? c?t? de lui
- Le Comte de Monte-Cristo
-----------------------------------------------------------------------
[quote="dongdm"][quote="dotri"]하나에 쓸걸, 자
나는 dotri님이 지적한 나쁜 예를 선호합니다.
K&R에도 언급된 바 있는 pointer to, array of, function () returning의 방법으로는 못 읽을 것이 없고 그 원리에 충실한 표기법이라고 봅니다. C/C++의 표기법의 원리에 준수한 표기라고 할까요. 괄호는 array of가 pointer to보다 더 나중에 다뤄져야 할때만 사용합니다.
- 죠커's blog / HanIRC:#CN
[quote="CN"][quote="dongdm"][quote="dotr
저도 첫번째 예를 선호하는 편입니다. typedef를 어떤 의미로 받아들이느냐 하는 차이인것 같습니다. dotri님께서 권하신 대로 해석을 하면, function을 typedef할때 오히려 헷갈릴 가능성이 크다고 봅니다.
----
Let's shut up and code.
[quote]나쁜 예: typedef struct b *BPTR;
저도 첫번째 예를 선호하는 편입니다.
typedef struct a__ *APTR, A;
충분히 가능한 코드죠?
typedef struct a__ * APTR, A;
typedef (struct a__ *) APTR, A;
세가지 중에서 어느 것이 가장 의미가 확실하게 들어올까요?
같은 이유로, *는 항상 변수 이름쪽에 붙여쓰는 편입니다.
int *a, b;
a와 b의 의미를 명백하게 표현하는 방식은 *를 변수 이름쪽에 붙이는 방식입니다.
덧붙여, C 프로그래머라면 기본적으로 *가 어느 위치에 있던간에 올바로 해석할 수 있는 능력을 갖춰야 된다고 생각합니다.
[quote="Anonymous"]덧붙여, C 프로그래머라면 기본적으
이 말씀이 맞습니다. 표기 위치 차이 때문에 구문을 이해하기가
어렵다면 아직 C 문법이 덜 익숙해서 그렇습니다.
참고로 저는 왼쪽에 붙여쓰는 편입니다만,
때로는 중립 위치에 놓기도 합니다. :lol:
Orion Project : http://orionids.org
리눅스 코딩표준문서에 이런문구가 있죠.Typedef is devi
리눅스 코딩표준문서에 이런문구가 있죠.
Typedef is devil...
Re: 포인터의 위치에 따른 해석차이
비슷한 논란이 예전에 있었던걸로 기억합니다. (찾아보면 나오겠지만 귀찮군요 ㅋ)
제가 위 글에서 지적하고 싶은건 한가지입니다.
위의 말씀은 모두 프로그래머가
"실수 했거나 또는 앞으로 실수할 가능성"을 어느정도 염두에
두고 그것을 미연에 방지하고자 명시적 캐스팅을 하지 않는것이
좋겠다는 전제를 깔고 있습니다.
하지만, 만약 메모리 할당받을 포인터 변수가 malloc 과
같은 라인에 있지 않다면 그리고 그것이 어떤 피치못할 이유에
의한것 일때 좌변값으로 사용된 포인터 변수의 타입을 일일이 찾아
읽기가 불편할것이고.. 또 그런 불편한 정도가 위에서 지적된
실수할 위험성보다 더 큰 비용을 초래하는 정도라고 판단될
경우에는 프로그래머의 의도를 명백히 해주고, 코드의 가독성을
높이기 위해 명시적 캐스팅을 하는것이 더 좋을 수 있습니다.
그렇지 않은 경우라면 일반적으로 명시적 캐스팅을 하지
않는것이 더 좋겠죠.
댓글 달기