NULL을 대입하는 것과 memset을 이용하는 것의 차이

krisna의 이미지

struct를 malloc으로 할당하고 각 포인터 변수에 직접 NULL을 대입하는 것과
memset을 이용하여 0으로 셋하는 것에 문제가 될만한 차이가 있습니까?

예를 들어

struct some_type {
	char *ptr1;
	void *ptr2;
	MyType *ptr3;
};

struct some_type *a = malloc(sizeof(struct some_type));

/* 1: 각각 NULL을 직접 대입하는 경우 */
a->ptr1 = NULL;
a->ptr2 = NULL;
a->ptr3 = NULL;

/* 2: memset으로 0으로 셋하는 경우 */
memset(a, 0, sizeof(struct some_type));

각각의 경우에 대해서 이식성이나 표준과 관련하여 문제가 있는지 궁금합니다.
특히 두번째 경우처럼 세팅을 하면 각 멤버 포인터들이 NULL이라고 확신할수 있는지요?

crimsoncream의 이미지

일반적으로 문제가 없지만 스펙에서 NULL이 수치 0과 완전한 등치라고 정의되어 있지는 않습니다. c89에서 산술연산이나 논리연산에서는 0으로 간주된다라고만 나왔던거 같은데. 기억이 정확한지 모르겠군요. 틀렸으면 다른 분들이 바로 잡아 주실 겁니다 :)

하여간 그렇게 볼때 memset으로 하면 연산을 통하지 않고 직접 메모리영역에다가 작업해버리니까 내부적으로 NULL을 0이 아닌 값으로 구현하고 컴파일러가 연산작업에 대한 처리를 해주는 플랫폼에 포팅하신다면 문제가 생길 수 있겠죠.

오늘 우리는 동지를 땅에 묻었습니다. 그러나 땅은 이제 우리들의 것입니다.
아직도 우리의 적은 강합니다. 그러나 우리는 그들보다 많습니다.
항상 많을 것입니다.

cdpark의 이미지

C 언어에서는 아무 문제가 없습니다. 하지만 C++에서는 조금 치명적일 수 있습니다. (아래 struct를 상속해서 사용할 가능성이 0.0001%라도 있다면..)

purewell의 이미지

_____________________________
언제나 맑고픈 샘이가...
http://purewell.biz

전웅의 이미지

krisna wrote:
struct를 malloc으로 할당하고 각 포인터 변수에 직접 NULL을 대입하는 것과
memset을 이용하여 0으로 셋하는 것에 문제가 될만한 차이가 있습니까?

예를 들어

struct some_type {
	char *ptr1;
	void *ptr2;
	MyType *ptr3;
};

struct some_type *a = malloc(sizeof(struct some_type));

/* 1: 각각 NULL을 직접 대입하는 경우 */
a->ptr1 = NULL;
a->ptr2 = NULL;
a->ptr3 = NULL;

/* 2: memset으로 0으로 셋하는 경우 */
memset(a, 0, sizeof(struct some_type));

각각의 경우에 대해서 이식성이나 표준과 관련하여 문제가 있는지 궁금합니다.
특히 두번째 경우처럼 세팅을 하면 각 멤버 포인터들이 NULL이라고 확신할수 있는지요?

일반적인 implementation 을 생각하고 계신 것이 아니라, 이식성과 표준을
생각하고 계신 것이라면 문제가 있습니다. 표준이 보장하는 이식성에 따르
면, 위와 같은 코드에서 각각의 ptrn 은 null pointer 로 초기화된다는 보
장이 전혀 없습니다! 표준이 제공하는 이식성을 생각한다면, memset() 은
거의 무용지물입니다. 현재의 표준이 보장하는 memset() 의 이식성있는 사
용은,

(1) memset() 으로 unsigned char 형의 배열에 값을 넣는다

(2) memset() 으로 0 ~ SCHAR_MAX 의 값을 char 혹은 signed char 형의 배
열에 넣는다

뿐이 없습니다. 또한, 앞으로 수정될 표준에서는 정수형의 (padding bit 를
포함한) 모든 bit 가 0 인 경우 정상적인 0 값을 갖도록 "추천" (요구나 보
장은 아닙니다) 할 것이기에,

(3) memset() 으로 정수형의 배열에 0 값을 넣는다.

역시 "의존할만한" 행동이 될 것입니다.

하지만, 부동형이나 포인터형과는 아무런 관련이 없으므로 위와 같은 행동
은 앞으로도 이식성을 보장받지 못합니다 - 실제로 null pointer 의 내부
표현이 0 이 아닌 환경도 존재한답니다.

정상적인 null pointer 를 만드는 이식성 있는 방법은 명시적이든 아니든
NULL 이나 null pointer constant 를 사용하는 것 뿐입니다.

그럼...

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

체스맨의 이미지

전웅 wrote:
실제로 null pointer 의 내부
표현이 0 이 아닌 환경도 존재한답니다.

이건 정말 심각한(또는 최악의) 환경이군요.
실제 예를 알고싶습니다.

많은 사람들이 쓰는 다음과 같은 코드들이 모두 버그 투성이가 되겠군요.

p = malloc( 1024 );
if( p ) { ... }

등등.

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
전웅 wrote:
실제로 null pointer 의 내부
표현이 0 이 아닌 환경도 존재한답니다.

이건 정말 심각한(또는 최악의) 환경이군요.
실제 예를 알고싶습니다.

모두 익숙치 않은 오래된 것들입니다. 살짝 책을 컨닝해서 적자면, CDC
Cyber 180 시리즈나 Honeywell-Bull 의 메인프레임 기종이 그렇다고 하는군
요. 현재 널리 사용되는 machine 중에서도 그와 같은 경우가 있는지 모르겠
으나, segmented architecture 에서 포인터 크기를 세분화하는 경우 가능한
경우를 상상할 수는 있습니다. 무엇보다도 중요한 것은 C 언어의 표준이 그
와 같은 환경을 금지하지 않았기에 어떠한 이유로든 그와 같은 machine 이
적법한 C 언어의 환경으로 나올 수 있다는 것입니다.

체스맨 wrote:
많은 사람들이 쓰는 다음과 같은 코드들이 모두 버그 투성이가 되겠군요.

p = malloc( 1024 );
if( p ) { ... }

등등.

아, 그렇지는 않습니다.

if (p) { ... }

if ((p) != 0) { ... }

와 동등한 의미를 갖고, MULL 과 null pointer 에 대한 다른 thread 에서도
말씀드렸듯이,

http://bbs.kldp.org/viewtopic.php?t=22763

0 은 정수 상수인 동시에 null pointer constant 라는 독특한 역할을 갖고
있습니다. 즉, 0 값을 갖는 정수 상수 수식이나, (void *) 로 변환되는 0
값을 갖는 정수 상수 수식은 포인터가 관련된 문맥에서 null pointer 로 변
환된다고 정의되어 있는 것입니다. 따라서,

if ((p) != 0) { ... }

는 정의에 의해 0 은 p 가 갖는 데이터형의 null pointer 값으로 변환됩니
다. 이때 null pointer 의 내부 표현에 0 이 아닌 값을 사용하는
implementation 은 적절한 내부 표현을 생성할 수 있는 기회를 갖게 됩니다.
하지만,

memset(p, 0, sizeof(p));

의 경우 null pointer constant 가 적절한 null pointer 를 생성할 수 있도
록 정의된 문맥이 아니기 때문에 그냥 p 의 모든 비트를 0 으로 만드는 결
과를 가져오게 됩니다. 이때 null pointer 의 내부 표현에 0 이 아닌 값을
사용하는 implemetnation 에서는 p 가 당연히 null pointer 로 인식되지 않
을 것입니다 - 심각하게 invalid pointer value 로 인식되는 경우 그보다
더 안 좋은 결과를 낳을 수도 있습니다.

즉, 표준은 implementation 에게 null pointer 의 특별한 내부 표현을 강요
하지 않으면서, implementation 이 정상적인 null pointer 를 생성할 수 있
는 기회를 갖도록 null pointer 가 생성되는 문맥을 따로 정의해주고 있는
것입니다. 위에서 if 문은 그와 같은 문맥에 해당하며, memset() 은 그렇지
않은 경우입니다. 따라서, 정상적인 null pointer 생성이 보장되는 문맥을
제대로 사용한다면 null pointer 의 내부 표현에 대한 걱정 없이 null
pointer 얻을 수 있습니다.

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

체스맨의 이미지

if( p ) 가 암묵적으로 제대로 처리되더라도
여전히 잠재적인 문제가 남는 것은, 포인터를 정수로 캐스팅해서
저장하고 있는 경우 그 값에 대해 조건문을 적용하는 경우입니다.

NULL 이 0 이 아닌 시스템이라도, malloc 이 제대로 할당된 포인터로서
0을 리턴하는 시스템은 없을 거라 생각되는데요. 혹 0을 사용가능한
포인터로 리턴하는 시스템이라도 위와같은 일말의 문제를 없애기 위해
저같으면, 0 인 NULL 을 리턴하는 malloc 을 새로 정의할 것 같습니다.
대충 다음처럼요.

void* not_used;
void* my_malloc( size_t size )
{
  retry:
  void* p = malloc( size );
  if( p==NULL ) return 0;
  if( p==0 ) { /* 혹여 이런 상황이 벌어지더라도, 그 포인터는 사용안함 */
    not_used = p; /* <--- install atexit to remove it */
    goto retry;
  }
  return p;
}
#define malloc my_malloc

NULL 은 0이라 믿는 코드들이 C 가 탄생한 이후로 상당히
쌓여있을거라 봅니다. memset 으로 구조체를 clear 하는 코드도
마찬가지구요.

Orion Project : http://orionids.org

mastercho의 이미지

C++책에서는 NULL 을 0으로 가정하고 있습니다

그 유명한 스캇 마이어의 Effective C++도 NULL은 0으로 규정하고 있죠

널 포인터를 int 형의 0이 아닌 여러가지 포인터형이나 다른 형으로

써볼려는 노력하는 모습도 보여주는데

C++에서는 NULL을 0으로 쓸수 밖에 없는 이유를 들며

설명을 합니다

자세히 기억은 안나지만요

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

전웅의 이미지

체스맨 wrote:
if( p ) 가 암묵적으로 제대로 처리되더라도
여전히 잠재적인 문제가 남는 것은, 포인터를 정수로 캐스팅해서
저장하고 있는 경우 그 값에 대해 조건문을 적용하는 경우입니다.

포인터 값을 정수형으로 변환하는 행동은 표준 차원에서 아예 이식성이 없
습니다. 따라서, null pointer 의 값을 정수형으로 변환한 결과가 0 이라는
보장은 없습니다. 반면, 0 값의 정수 상수 수식을 포인터형으로 변환하는
행동은 그와 같은 정수 상수 수식이 null pointer constant 로 특별히 정의
되어 있기 때문에 (null pointer 가 어떤 내부 표현을 갖든) null pointer
를 정상적으로 생성한다는 사실이 보장됩니다.

체스맨 wrote:
NULL 이 0 이 아닌 시스템이라도, malloc 이 제대로 할당된 포인터로서
0을 리턴하는 시스템은 없을 거라 생각되는데요. 혹 0을 사용가능한
포인터로 리턴하는 시스템이라도 위와같은 일말의 문제를 없애기 위해
저같으면, 0 인 NULL 을 리턴하는 malloc 을 새로 정의할 것 같습니다.
대충 다음처럼요.

void* not_used;
void* my_malloc( size_t size )
{
  retry:
  void* p = malloc( size );
  if( p==NULL ) return 0;
  if( p==0 ) { /* 혹여 이런 상황이 벌어지더라도, 그 포인터는 사용안함 */
    not_used = p; /* <--- install atexit to remove it */
    goto retry;
  }
  return p;
}
#define malloc my_malloc

NULL, null pointer, null pointer constant 에 대해서 많은 부분을 오해하
고 계십니다. NULL 은 (implementation 이 정의한 형태의) mull pointer
constant 일뿐입니다. NULL 과 0, ((void *)0) 은 모두 null pointer
constant 로서 올바른 프로그램이라면 모두 같은 곳에 사용될 수 있습니다.
null pointer 에 정수 0 과 다른 내부 표현을 사용한다고 해서 NULL 을 그
값으로 정의해야 하는 것은 아닙니다 - 그러한 특이한 implementation 에서
도 표준의 정의에 의해 NULL 을 0 이나 ((void *)0) 으로 정의할 수 있습니
다. 따라서, 위에서 (p == NULL) 와 (p == 0) 는 표준을 따르는
implementation 에서 완전히 같은 결과를 보이게 됩니다. 제가 null
pointer 의 내부 표현이 정수값 0 과 다를 수 있다고 설명한 것은, if (p
== 0) 와 같이 null pointer 생성이 보장되는 문맥에서도 다른 결과를 보일
수 있다고 설명드린 것은 아닙니다 - 제 이전 글을 다시 읽어보시기를 부탁
드립니다.

0 은 0 값을 의미하는 정수 상수 수식이면서 동시에 0 이라는 값과는 무관
한 null pointer constant 의 역할을 수행하는 것입니다 - 즉, 0 값을 갖는
정수 상수 수식의 의미가 중의적으로 정의되어 있는 것입니다. 따라서,
(정수 상수가 아닌) null pointer constant 로서 0 을 0 이라는 값과 연관
지어 생각하시는 것은 잘못된 것입니다.

이와 관련된 내용은 C FAQs 의 5장이나 제 책의 null pointer 부분을 참고
하시면 도움이 될 것 같습니다 - NULL, null pointer, null pointer
constant 와 관련된 문제는 오해의 여지가 많습니다. 이를 완벽하게 이해하
지 못한 상태에서 제가 드리는 간략한 설명은 또 다른 오해를 낳을 가능성
이 있습니다.

체스맨 wrote:
NULL 은 0이라 믿는 코드들이 C 가 탄생한 이후로 상당히
쌓여있을거라 봅니다. memset 으로 구조체를 clear 하는 코드도
마찬가지구요.

"NULL 은 0 이라고 믿는" 이 아니라 보다 정확히는 "null pointer 의 내부
표현은 정수 0 과 동일하다고 믿는" 이 의도하신 표현이라고 생각합니다.
그와 같은 코드는 표준이 보장하지 않는 사실에 의존한 "잘못된" 것입니다.
memset 으로 구조체를 clear 하는 것 역시 다수의 implementation 에서 의
도한 효과를 보이겠지만, 포인터와 관련된 문제와 부동형, 정수형의
padding bit 와 관련된 문제로 인해 표준이 보장하는 바는 아닙니다.

그럼...

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

체스맨의 이미지

음... 대충 자료를 찾으니, malloc 을 재정의해서 해결될만한 문제는
아니군요.

한가지만 더 확인 바랍니다. 이것 관련 자료는 아직 찾지 못했거든요.

전역변수는 초기에 0으로 초기화되도록 되어 있습니다.
이때 전역 포인터는 0으로 되는지요, 아니면, NULL 로 되는지요?

제가 쓴 코드들을 다시 검토해봐야 겠습니다.
이 부분은 정말 고려하지 못했던 것이군요.

참고 자료입니다.
http://www.eskimo.com/~scs/C-faq/q5.17.html
http://www.angelfire.com/mo3/martin0/c/c_std_lib/null_ptr.html
http://www.help119.com/chair/cdata/cfaq_kr/node9.html

Orion Project : http://orionids.org

체스맨의 이미지

답변을 빨리 달아주셨군요. 저도 꽤 빨리 올렸는데. :)
MS 윈도우즈의 API 중 일부는 포인터를 정수로 캐스팅하도록
어떤면에서는 강요하고 있습니다.

이 부분은 잠재적으로 프로그래머에게 더 많은 오류를 범하도록
만들 수 있을 것 같습니다.

물론 MS 윈도우즈가 NULL 이 내부적으로 0 이 아닌 시스템에서
작동할 지는 의문이지만요. 8)

잘 배웠습니다.

아...
저 위에, 전역 변수 포인터 초기값은 어찌 되는 지 알려주셨으면 합니다.

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
답변을 빨리 달아주셨군요. 저도 꽤 빨리 올렸는데. :)

예, 낮에 큰맘먹고 노트북을 알아보기 위해 용산에 나갈 일이 있어서 외출
전에 급하게 포스팅했습니다 - 그래서 내용이 좀 부실합니다. :)

체스맨 wrote:
MS 윈도우즈의 API 중 일부는 포인터를 정수로 캐스팅하도록
어떤면에서는 강요하고 있습니다.

정수와 포인터 사이의 변환은 표준이 보장하지 않습니다. 하지만, 의도는
그와 같은 변환을 "잘못된" 행동으로 두려는 것이 아니라 implementation
이 필요한대로 의미를 부여하도록 하기 위해서 입니다. 가능하다면 어떤 부
분이 그와 같은 변환에 의존하는지 알고 싶습니다. POSIX 의 경우 대상체
포인터의 generic 포인터인 void * 와 임의의 함수 포인터 사이의 변환에
의존하는 부분이 있기에 (void * 와 임의의 함수 포인터 사이의 변환은 C
표준에 의하면 정의되지 않습니다) 그리 놀라운 일은 아닙니다만 궁금합니
다.

체스맨 wrote:
이 부분은 잠재적으로 프로그래머에게 더 많은 오류를 범하도록
만들 수 있을 것 같습니다.

예. 자세한 내용을 알지 못하는 상태에서 MS-Windows 프로그래밍에 익숙해
진다면 C 언어의 일반적인 정의에 대한 오해를 낳을 가능성이 큰 부분 같습
니다.

체스맨 wrote:
아...
저 위에, 전역 변수 포인터 초기값은 어찌 되는 지 알려주셨으면 합니다.

체스맨 wrote:
전역변수는 초기에 0으로 초기화되도록 되어 있습니다.
이때 전역 포인터는 0으로 되는지요, 아니면, NULL 로 되는지요?

일단 null pointer constant 로서의 0 과 NULL 에는 근본적인 차이가 없음
을 말씀드렸습니다 - NULL 은 null pointer constant 의 가독성을 위한 일
종의 "껍질" 이라고 이해하셔도 좋습니다. 물론, NULL 이 잘못 사용된 프로
그램에서 기대되는 행동을 보일 수 있도록 각 implementation 이 고민해서
NULL 을 여러 형태 (0L, ((void *)0) 등) 로 정의하고 있지만, 기본적으로
올바른 프로그램에서는 0 이 사용될 곳에 NULL 을 쓰나 혹은 그 반대로 쓰
나 프로그램의 행동은 영향받지 않습니다.

그리고, 초기치가 명시적으로 제공되지 않은 전역 변수는 초기화되는 대상
이 포인터형인 경우 null pointer 로 초기화됨이 보장됩니다. 따라서,

char *p; // global

char *p = 0; // global

혹은

#include <stddef.h>    // for NULL
char *p = NULL;    // global

와 완전히 동일합니다.

체스맨 wrote:
제가 쓴 코드들을 다시 검토해봐야 겠습니다.
이 부분은 정말 고려하지 못했던 것이군요.

올바른 관례를 따르는 다수의 프로그램은 null pointer 에 대한 완벽한 이
해가 없더라도 null pointer 를 올바르게 생성해서 사용하고 있습니다. 이
thread 의 주제였던 memset() 과 같은 편법을 사용하지 않는 이상, 대부분
의 부분은 올바르게 null pointer 가 생성되는 문맥이었으리라 생각합니다
- 너무 크게 걱정 안하셔도 될 것 같은 느낌이 듭니다. 다만, 다음과 같은
경우는 null pointer 에 대해 잘 알고 계신 분들도 쉽게 실수하는 부분입니
다.

void func(char *s, ...);

func("foo", "bar", "foobar", 0);

func() 이 null pointer 를 끝으로 검사해 가변 개수의 string 을 처리하는
경우, 0 이 주어진 문맥이 포인터로 변환되는 문맥이 아니기에 (가변 인자
에 해당하기에 대응하는 매개변수에 대한 정보가 존재하지 않습니다) null
pointer constant 로 의도된 0 은 적절한 데이터형 (여기서는 char *) 의
null pointer 로 변화되지 않고, 정수 상수의 의미인 0 으로 전달됩니다 -
결국 "잘못된" 프로그램이 되어 버립니다. 따라서, 이 경우 명시적으로

func("foo", "bar", "foobar", (char *)0);

라고 해주어야 합니다. 혹은 char * 와 void * 는 완전히 동일한 내부 표현
을 사용하기 때문에, implementation 이 위와 같은 잘못된 경우를 배려하기
위해 NULL 을 ((void *)0) 으로 정의해주는 경우,

func("foo", "bar", "foobar", NULL);

로 써주는 것은 잘못된 프로그램이지만 implementation 의 배려로 우연히
올바르게 행동할 수 있습니다. 물론, NULL 을 사용하더라도 이식성을 갖는
올바른 프로그램이 되기 위해서는

func("foo", "bar", "foobar", (char *)NULL);

로 써주어야 합니다. 참고로, func() 과 같은 특성을 갖는 대표적인 함수로
는 exec() 가 있습니다.

그럼...

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

전웅의 이미지

mastercho wrote:
C++책에서는 NULL 을 0으로 가정하고 있습니다

그 유명한 스캇 마이어의 Effective C++도 NULL은 0으로 규정하고 있죠

널 포인터를 int 형의 0이 아닌 여러가지 포인터형이나 다른 형으로

써볼려는 노력하는 모습도 보여주는데

C++에서는 NULL을 0으로 쓸수 밖에 없는 이유를 들며

설명을 합니다

자세히 기억은 안나지만요

C++ 의 null pointer constant 에 대한 정의는 C 와는 다소 다릅니다. C++
의 경우 기본적으로 generic pointer 인 void * 와의 변환에 명시적인 캐스
트 연산을 요구하기 때문에 ((void *)0) 형태를 null pointer constant 로
정의하지 않습니다. 반면, C 는 0 의 값을 갖는 정수 상수 수식 혹은 void
* 형으로 변환되는 그런 상수 수식을 null pointer constant 로 정의하고
있습니다 - 신기한 것은 그와 같은 추가적인 형태의 null pointer constant
는 (다른 void * 형 포인터와는 달리) 함수 포인터와의 변환도 허락됩니다.

예를 들어, 다소 변태(?)적이지만,

#define NULL (4-4)

도 C/C++ 에서 적법한 NULL 의 정의입니다.

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

체스맨의 이미지

윈도우즈 프로그래밍에서는 많은 경우 long 에서 포인터로, 그리고
포인터에서 long 으로 캐스팅이 일어납니다. 대표적인 경우가 윈도우
메시지 핸들러인

long _stdcall WindowProc( void* hwnd, unsigned msg, unsigned wp, long lp );

입니다. 만일 lp 가 적당한 포인터인지 판단하기 위해 if( lp )
처럼 했다면, NULL 이 내부적으로 0이 아닌 시스템에서는 오류겠죠.
적어도 if( (void*)lp ) 라고 해야 하겠죠.

윈도우즈는 UI 개체에 포인터를 달아주는 많은 경우에서 long 과 pointer 가
왔다 갔다 하는 구조로 돼 있습니다.

아무튼 전역 변수가 NULL 로 되는 것은 다행스런 일이군요. :)
단, 저도 memset 으로 구조체를 클리어한 일이 있어서 뒤져봐야 겠습니다. :lol:

가변인자 함수에 대해서는 NULL 을 0으로 쓴일이 없습니다. :wink:

Orion Project : http://orionids.org

crimsoncream의 이미지

Win32 API는 MS Windows 플랫폼이라는 한정된 용도를 가지고 있고 Win32 API가 컴파일러 구현과 분리 될 생긱을 하고 만들어진 건 아닌 것 같으니.
아무 문제가 아닐 것 같은데요.

Win32 API를 지원하는 compiler의 스펙이 NULL이 O과 등치일것 하면 끝나는 문제고 Win32 API가 NULL을 (long)0으로 casting을 한다면 그런 스펙이 명시적인든 아니든 존재한다는 거겠죠.

오늘 우리는 동지를 땅에 묻었습니다. 그러나 땅은 이제 우리들의 것입니다.
아직도 우리의 적은 강합니다. 그러나 우리는 그들보다 많습니다.
항상 많을 것입니다.

체스맨의 이미지

그래서 이미 윈도가 NULL 이 내부적으로 0 이 아닌 시스템으로
포팅될 가능성이 있겠느냐 말씀드렸구요.

하지만, Wine 처럼, WinAPI 를 유닉스 시스템을 위해 구현한 경우라면,
리눅스처럼 폭넓은 호환성이 있는 시스템이 어디에 포팅되었는가에따라,
아주 작은 가능성으로 저런 것도 문제는 될 수도 있겠죠. 물론,
NULL 포인터를 잘 이해하고 사용하면 문제가 없겠구요.

아무튼 저는 아주 기초적인 내용이면서도 전혀 고려를 못하고 있었다는게
참 그렇네요. -_-;

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
윈도우즈 프로그래밍에서는 많은 경우 long 에서 포인터로, 그리고
포인터에서 long 으로 캐스팅이 일어납니다. 대표적인 경우가 윈도우
메시지 핸들러인

long _stdcall WindowProc( void* hwnd, unsigned msg, unsigned wp, long lp );

입니다. 만일 lp 가 적당한 포인터인지 판단하기 위해 if( lp )
처럼 했다면, NULL 이 내부적으로 0이 아닌 시스템에서는 오류겠죠.
적어도 if( (void*)lp ) 라고 해야 하겠죠.

lp 에 포인터가 정수로 변환된 값이 저장된 상태에서 해당 포인터 값이
null pointer 인지 확인하기 위해

if (lp)

와 같은 구조를 사용한다면 이는 다음과 같은 가정에 의존한 것입니다.

- null pointer 를 정수형으로 변환한 값은 0 이며, null pointer 가 아닌
다른 유효한 포인터를 정수형으로 변환한 값은 0 이 아니다.

또한,

if ((void *)lp)

와 같은 구조는 (위의 가정에 이어) 다음과 같은 가정에 의존합니다.

- 만약, 실행시 생성된 정수값 0 을 포인터형으로 변환하면 null pointer
가 된다.

물론, null pointer 의 내부 표현에 0 (all bits zero) 을 사용하는 대부분
의 implementation 에서 이 두 가정은 모두 참일 것입니다. 하지만, 여기서
한가지 주의하실 점은 if ((void *)lp) 형태가 그 의도를 분명히 한다는 점
에서는 바람직하지만, lp 의 값이 0 이라고 해도 null pointer 가 올바르게
생성된다는 (표준 차원에서의) 보장은 없다는 사실입니다. 즉,

int i = 0;
char *p1 = (char *)0;    /* okay, null pointer guaranteed */
char *p2 = (char *)i;    /* implementation-defined conversion */

와 같은 예에서 p1 은 null pointer 가 됨이 보장되지만, p2 는 정수와 포
인터 사이의 이식성 없는 변환에 의존합니다. 그 이유는 0 값을 갖는 정수
"상수" 수식만 null pointer constant 이기 때문입니다 - null pointer 와
관련된 내용은 혼동스럽습니다. C 언어가 null poiner constant 를 위해
null 같은 별도의 keyword 를 도입하지 않고, 기존의 관례를 따라 0 값의
정수 상수 수식에 null pointer constant 의 의미를 덧붙이다보니 언어의
정의를 제대로 이해하는 것이 쉽지 않게 되어버렸습니다.

물론, MS-Windows 가 위와 같은 형태의 API 를 지원한다면, 자연스럽게
포인터 <-> 정수 사이의 변환 역시 해당 API 를 사용하는데 적합하도록 정
의하고 있을 것입니다. 물론, 해당 코드를 전혀 다른 환경으로 이식할 때는
문제가 되는 부분입니다.

체스맨 wrote:
아무튼 전역 변수가 NULL 로 되는 것은 다행스런 일이군요. :)
단, 저도 memset 으로 구조체를 클리어한 일이 있어서 뒤져봐야 겠습니다. :lol:

가변인자 함수에 대해서는 NULL 을 0으로 쓴일이 없습니다. :wink:

null pointer 에 대한 논의에서 NULL 은 보통 <stddef.h> 를 포함한 다수의
헤더에 정의되어 있는 (null pointer 가 아닌 null pointer constant 를 의
미하는) NULL 매크로를 의미하는 것이 일반입니다. 위의 글에서 님은 (null
pointer constant 가 아닌) null pointer 를 의미하기 위해 NULL 이라는 용
어를 사용하신 것 같습니다 - 혹시나 이 thread 를 읽는 다른 분들의 혼동
을 막기 위해 말씀드린 것입니다.

그럼...

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

전웅의 이미지

crimsoncream wrote:
Win32 API를 지원하는 compiler의 스펙이 NULL이 O과 등치일것 하면 끝나는 문제고

NULL 은 "null pointer 의 값" 이 아닌 "null pointer constant 를 의미하
는 매크로" 를 지칭합니다. null pointer 에 0 (all bits zero) 이 아닌 다
른 표현을 사용하는 implementation 에서도 (C 혹은 C++) 표준의 요구를 만
족하기 위해 대부분 매크로 NULL 을 0 (혹은 그와 유사한 형태) 으로 정의
(#define) 합니다. 따라서, 포인터가 정수형으로 변환된 값이 저장되어 있
는 lp 에 대해서

if (lp)
if ((void *)lp)

와 같은 행동을 하는 것이 올바른 것이라면, Win32 API 를 지원하는 컴파일
러의 올바른 spec 은 위에서 말씀드린 (표준은 보장해주지 않는) 두 가지
가정을 만족해야 합니다.

참고로 말씀드리면, null pointer 와 관련된 논의를 하다보면

null pointer
null pointer constant
NULL
(간혹 NUL)

과 같은 용어가 서로 다른 의미로 사용되어 많은 오해를 낳게 됩니다. 따라
서, 이 용어들의 올바른 정의를 분명히 구분하여 사용하는 것이 의사소통에
매우 중요하다고 생각합니다.

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

체스맨의 이미지

전웅 wrote:
- 만약, 실행시 생성된 정수값 0 을 포인터형으로 변환하면 null pointer
가 된다.

int i = 0; 
char *p1 = (char *)0;    /* okay, null pointer guaranteed */ 
char *p2 = (char *)i;    /* implementation-defined conversion */

그것을 의미한 것은 아니라,

long i = (long)(char*)0;
char* p = (char*)i;

이것을 의미한 것이었습니다. 실제로 윈도우 프로시저로 위의
i 와 같이 일단 포인터-->정수로 캐스팅 된 값이 넘어올 수 있다는
의미에서 if( (void*)lp ) 가 적절하다고 말씀 드린 것입니다.

아쉽게도 이 사항을 고려해오진 못했지만, 그래도 그간 누적된 경험으로
이해는 빨리 했습니다. :wink:

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
전웅 wrote:
- 만약, 실행시 생성된 정수값 0 을 포인터형으로 변환하면 null pointer
가 된다.

int i = 0; 
char *p1 = (char *)0;    /* okay, null pointer guaranteed */ 
char *p2 = (char *)i;    /* implementation-defined conversion */

그것을 의미한 것은 아니라,

long i = (long)(char*)0;
char* p = (char*)i;

이것을 의미한 것이었습니다.

예, 알고 있습니다. 제 말씀은 null pointer 의 값을 정수형으로 변환한 값
이 0 이라고 해도 (님의 예에서 i 의 값), 그 값을 다시 포인터형으로 변환
한 결과가 null pointer 라는 것은 또 다른 가정을 필요로 하는 문제라는
뜻입니다. 또한, null pointer 의 값을 정수형으로 변환한 값이 0 이 아닐
지라도 그 값을 포인터형으로 변환한 결과가 null pointer 라는 것 역시 유
사한 가정에 의존한다는 것입니다.

즉,

char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if (lp)

는 말씀드린 첫번째 가정

Quote:
- null pointer 를 정수형으로 변환한 값은 0 이며, null pointer 가 아닌
다른 유효한 포인터를 정수형으로 변환한 값은 0 이 아니다.

에 의존하며

char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if ((void *)lp)     // another implementation-defined conversion

자체는 null pointer 의 값을 정수형으로 변환한 후에 (그 값이 무엇이든)
다시 포인터형으로 변환하면 null pointer 의 값이 나온다는 것을 가정하고
있습니다. 그리고, 위 두 형태의 코드가 모두 해당 API 에서 허용되는 것이
라면 그 두 가정을 합하여 제가 위에서 말씀드린 가정

Quote:
- null pointer 를 정수형으로 변환한 값은 0 이며, null pointer 가 아닌
다른 유효한 포인터를 정수형으로 변환한 값은 0 이 아니다.

- 만약, 실행시 생성된 정수값 0 을 포인터형으로 변환하면 null pointer
가 된다.

이 되는 것입니다.

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

체스맨의 이미지

간단히 예 아니오 식으로 정리를 좀 해보겠습니다.
전웅님 글은 제가 이해하기 좀 어려운 경우가 가끔 있습니다.

일단 저는

char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if (lp)

(1)위 코드는 시스템에 따라 오류를 발생할 수 있다.
(2) 시스템에 따라, lp 값은 0이 아닐 수도 있다. ( 이것이 (1)의 이유가 된다고 봅니다. )

char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if ((void *)lp)     // another implementation-defined conversion

(3) 위 코드는 모든 시스템에서 문제를 일으키지 않는다.

제가 의미한 것은 (1)(2)(3) 입니다.
각 항목이 Y인지 N인지 말씀해 주시면 제가 정리가 될 것 같습니다.

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
간단히 예 아니오 식으로 정리를 좀 해보겠습니다.
전웅님 글은 제가 이해하기 좀 어려운 경우가 가끔 있습니다.

죄송합니다. 글을 친절하게 잘 쓰는 편이 아니라... T.T

체스맨 wrote:
일단 저는

char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if (lp)

(1)위 코드는 시스템에 따라 오류를 발생할 수 있다.
(2) 시스템에 따라, lp 값은 0이 아닐 수도 있다. ( 이것이 (1)의 이유가 된다고 봅니다. )

(1) Yes

일반적으로는 implementation 이 정의해준 어떤 의미있는 변환일 가능성이
크지만, 표준은 변환이 성공적으로 수행된다는 보장을 해주지 않습니다. 따
라서, 그 변환이라는 행위 자체가 시스템에 따라 문제를 일으킬 수 있으며,
변환된 결과가 유효한 정수값 (예를 들면, int 형에 padding bit 로서
parity bit 를 추가로 사용하는 경우를 생각해 볼 수 있습니다) 이 아니라
서 if 문에서처럼 사용할 때 문제를 일으킬 수도 있습니다. 따라서 답은
Yes 가 됩니다.

(2) Yes

당연히 아닐 수 있습니다. 말씀드렸듯이 null pointer 의 내부 표현에 단순
한 0 을 사용하고, 포인터와 정수 사이의 변환을 단순한 내부 표현의 재해
석 (즉, 포인터의 bit 열을 간단히 정수로 해석) 으로 다루는 경우 0 이 나
올 가능성이 큽니다만, 위에서 말씀드렸듯이 다른 가능성이 얼마든지 있습
니다. 따라서, 표준을 생각한다면 0 이라고 보장되지 않습니다.

체스맨 wrote:
char *p = 0;        // null pointer
int lp = (int)p;    // implementation-defined conversion
if ((void *)lp)     // another implementation-defined conversion

(3) 위 코드는 모든 시스템에서 문제를 일으키지 않는다.

(3) No

(1) 과 마찬가지로 정수를 포인터로 변환하는 것 역시 동일한 문제를 겪습
니다. 일반적으로는 implementation 이 정의해준 어떤 의미있는 변환일 가
능성이 크지만, 표준이 보장해주는 바는 훨씬 약합니다. "포인터값 -> 정수
-> 포인터값" 이 원래의 포인터값을 복원해 준다는 보장은 없습니다. 더구
나 (포인터값을 변환해 나왔든 그렇지 않든 상관 없이) "정수 -> 포인터값"
으로의 변환 역시 잘못된 포인터값을 생성할 수 있습니다. 일단, "포인터값
-> 정수" 의 변환은 그 변환 자체가 문제를 일으킬 수 있어도, "정수 -> 포
인터값" 의 변환 자체는 문제를 일으키지는 않습니다 - 즉, 어떤 포인터값
을 생성해 주기는 합니다. 문제는 그 포인터값이 엉뚱한 곳을 가리키고 있
거나, 제대로 정렬되어 있지 않을 수 있다는 것입니다. 따라서, 바로 위에
있는 예제의 if 문에서처럼 그 포인터값을 사용하는 것은 의미있는 정의된
행동이 보장되지 않습니다.

최대한 자세히 또 오해 없이 설명하려고 노력했는데 결과는 어떨지 모르겠
습니다.

그럼...

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

체스맨의 이미지

가끔 어려운 문장들이 있는 것 같습니다. :)

문제는 3번인데요,
간단히는, sizeof(void*)==sizeof(long) 이라는 전제하에,
void* p = (void*)(long)(void*)0;
일때 p 가 NULL 이 아닌 시스템도 있을 수 있다.
이것은 표준이 정의하는 바가 아니다 라는 말씀이죠?

그렇다면, 윈도우즈 같은 구현은 그 구현 자체의 한계로
포팅될 수 있는 환경의 제약이 (적게나마)있다고 결론 지을 수 있을까요?

만일 그렇다면 덧붙여, 윈도우 프로시저 프로토타입의 마지막 인자가
적어도 void* 가 되는 편이 더 나았다고 볼 수 있을 것 같은데요.

Orion Project : http://orionids.org

전웅의 이미지

체스맨 wrote:
가끔 어려운 문장들이 있는 것 같습니다. :)

제 표현의 한계인듯 합니다. T.T

체스맨 wrote:
문제는 3번인데요,
간단히는, sizeof(void*)==sizeof(long) 이라는 전제하에,
void* p = (void*)(long)(void*)0;
일때 p 가 NULL 이 아닌 시스템도 있을 수 있다.
이것은 표준이 정의하는 바가 아니다 라는 말씀이죠?

옙. 표준에 의하면, null pointer 가 아닐 뿐더러 유효한 포인터가 아닐 수
도 있습니다.

체스맨 wrote:
그렇다면, 윈도우즈 같은 구현은 그 구현 자체의 한계로
포팅될 수 있는 환경의 제약이 (적게나마)있다고 결론 지을 수 있을까요?

예, 가능합니다. 해당 API 에서 그와 같은 변환 (정수 <-> 포인터 변환) 과
사용이 적법한 것이라면, 전혀 다른 환경으로 그 API 와 API 를 사용하는
프로그램을 이식할 때 어떻게든 그와 같은 변환이 의존하는 가정이 만족되
어야 합니다.

체스맨 wrote:
만일 그렇다면 덧붙여, 윈도우 프로시저 프로토타입의 마지막 인자가
적어도 void* 가 되는 편이 더 나았다고 볼 수 있을 것 같은데요.

만약, 순수한 정수값이 전달될 기회가 없고 일관되게 대상체 포인터
(pointer to object) 가 전달되는 함수였다면, void * 가 바람직한 선택임
에 틀림 없습니다. 혹은 순수한 정수값과 포인터값이 하나의 매개변수를 통
해서 전달될 가능성이 있다면, 공용체를 사용하는 것이 더 좋았을 것입니다
- 유사한 이야기는 POSIX 에도 적용됩니다.

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

체스맨의 이미지

윈도우 프로시저의 마지막 long 인자에는,
정수도 전달되지만, 포인터도 빈번히 전달 됩니다.
결론대로라면, 정수가 전달되더라도, 정수의 포인터를
전달하는 편이 더 나았을 것 같네요. 물론 포인터
값을 얻는 인스트럭션이 하나 더 들어가겠지만,
성능 감소를 논할만한 것도 아니었을테구요.

X Window 처럼 공용체를 쓰는 것도 좋은 방법이었을텐데,
수많은 타입의 포인터들이 전달되고, 전달 될 것이었던 터라,
MS 프로그래머들도 생각을 가지고 프로토타입을 택했을텐데,
그게 정석은 못됐군요. 어차피 폭넓게 포팅될 운영체제는
아니었으니까요... 설마, 고려를 못했던 건 아니겠죠.

좋은 답변 감사합니다. 많은 도움이 됐습니다.

Orion Project : http://orionids.org

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.