2009-11-04 7 views
1

gcc 4.1.2를 사용하여 RHEL 5.1 64 비트 플랫폼에서 실행 중입니다.64 비트 시스템에서 NULL 정의 문제가 발생했습니다.

void str_concat(char *buff, int buffSize, ...); 

마지막 인수는 NULL이어야 인수의 끝을 지정하는 동안, 가변 목록 (...)에 전달 된 문자 *를 concats :

나는 유틸리티 기능을 가지고있다. 64 비트 시스템에서는 NULL이 8 바이트입니다.

문제가 생겼습니다. 내 응용 프로그램은 직접/간접적으로 2 stddef.h 파일을 포함합니다.

첫번째는 다음과 같이 정의 NULL /usr/include/linux/stddef.h이다

#undef NULL 
#if defined(__cplusplus) 
#define NULL 0 
#else 
#define NULL ((void *)0) 
#endif 

두번째 인은/usr/LIB/GCC/x86_64에-레드햇 리눅스/4.1.2/포함/stddef.h와 1 일 정수 0 (4 바이트)로 정의하면서, __null (8 바이트)로 NULL 정의부터 물론

#if defined (_STDDEF_H) || defined (__need_NULL) 
#undef NULL  /* in case <stdio.h> has defined it. */ 
#ifdef __GNUG__ 
#define NULL __null 
#else /* G++ */ 
#ifndef __cplusplus 
#define NULL ((void *)0) 
#else /* C++ */ 
#define NULL 0 
#endif /* C++ */ 
#endif /* G++ */ 
#endif /* NULL not defined and <stddef.h> or need NULL. */ 
#undef __need_NULL 

내가 2 일이 필요합니다.

/usr/include/linux/stddef.h가 inderectly에 포함되지 않도록 어떻게합니까?

UPD :

  1. 컴파일 라인은 매우 간단합니다 :

    그램 ++ -Wall -fmessage 길이 = 당신의 많은 무효 (통과하는 것이 좋다 0 -g -pthread

  2. *) 0. 이것은 물론 작동합니다. 그 기능이 많은 곳에서 사용된다는 문제는 많은 곳을 의미합니다. 나는 C++ 표준이 약속하는 해결책을 찾고 싶다 - 8 바이트 크기의 NULL.

+0

컴파일 경로가 있으면 포함 경로에있는 디렉토리를 볼 수 있습니다. –

+0

생성 된 어셈블리 출력을 볼 수 있습니까? – Malkocoglu

+0

NULL이 NULL로 대체되었음을 증명하기 위해 파일 (gcc -E)을 사전 처리합니다. – dimba

답변

12

이 경우 "NULL 한정 문제"가 없습니다. 코드에서 NULL을 어떻게 사용하려고하는지에 문제가 있습니다.

NULL은 C/C++의 가변 함수에 이식 할 수 없습니다. 전달하기 전에 명시 적으로 캐스팅해야합니다. 예를 들어 (const char*) NULL을 인수 목록의 종결 자로 전달해야합니다.

귀하의 질문은 C++. 어떤 경우 든 크기에 관계없이 C++에서 NULL은 항상 정수 상수로 정의됩니다. C++에서 NULL을 포인터로 정의하는 것은 불법입니다. 함수가 포인터 (const char *)를 기대하기 때문에, C++ 코드에서 NULL의 정의가 작동하지 않습니다.

청소기 코드에 대해 당신은

const char* const STR_TERM = NULL; 

처럼, 자신의 상수를 정의 할 수 있습니다 및 함수에 대한 호출에 사용합니다. 그러나 그 의미로는 결코 NULL을 의미있게 사용할 수는 없습니다. 일반 NULL이 가변 인수로 전달 될 때마다 해결해야 할 뻔뻔스러운 이식성 버그입니다.

업데이트 : 업데이트가 "C++ 표준에서 8 바이트 크기 NULL을 약속합니다."(64 비트 플랫폼에서 예상 함). 이것은 단지 의미가 없습니다. C++ 표준은 그 비슷한 것을 약속하지 않는다. NULL.

NULL은 rvalue로 사용하기위한 것입니다. 특정 크기가 없으므로 실제 크기가 원격으로 중요 할 수도있는 NULL의 유효 사용이 없습니다. ISO/IEC에서 14,882 인용


: 1998, 섹션 18.1 '유형', 제 4 항 :

매크로 NULL은 구현에 C++ 널 포인터 상수가 정의이 규격 (4.10). 180)

180) 가능한 정의에는 0과 0L이 포함되지만 (void *) 0은 포함되지 않습니다.

+1

비 const 널 포인터를 전달할 수있을 정도로 완벽하게 수용 가능합니다. –

+0

"NULL은 이식 가능하게 C/C++의 가변 함수에 전달 될 수 없습니다." - 아니, C++에서는 할 수 없다. 나는 C로 괜찮다고 확신한다. –

+1

@Chris Lutz : C에서는 괜찮지 않다. 당신이 '0','0L','(void *) 0' 또는 다른 것을 전달하는지 어떻게 알 수 있을까? ? – AnT

0

이전에 나는 아래 답변으로 대답했다. 그러나 나는 여러 정보를 잘못 해석하고 잘못된 대답을했다는 것을 알았습니다. 그냥 호기심에서, 나는 VS2008과 동일한 테스트를했고 나는 다른 결과를 얻었다. 이

 
Why do you need the second one ? Both headers say the same thing. 
And it does not even matter if you write 0 or NULL or ((void *)0) 
All of them will take 8 bytes. 

내가 GCC 4.1.3

#include <string.h> 

void str_concat(char *po_buf, int pi_max, ...) 
{ 
    strcpy(po_buf, "Malkocoglu"); /* bogus */ 
} 

int main() 
{ 
    char buf[100]; 
    str_concat(buf, 100, "abc", 1234LL, "def", 5678LL, "ghi", 2345LL, "jkl", 6789LL, "mno", 3456LL, 0, "pqx", 0); 
    return 1; 
} 

와 64 비트 플랫폼에서 빠른 테스트를했다 ... 단지 뇌의 운동이며,이 컴파일러에 의해 생성 된 어셈블리입니다 ...

main: 
.LFB3: 
    pushq %rbp 
.LCFI3: 
    movq %rsp, %rbp 
.LCFI4: 
    subq $192, %rsp 
.LCFI5: 
    leaq -112(%rbp), %rdi 
    movl $0, 64(%rsp)       0 
    movq $.LC2, 56(%rsp)      "pqx" 
    movl $0, 48(%rsp)       0 
    movq $3456, 40(%rsp)      3456LL 
    movq $.LC3, 32(%rsp)      "mno" 
    movq $6789, 24(%rsp)      6789LL 
    movq $.LC4, 16(%rsp)      "jkl" 
    movq $2345, 8(%rsp)      2345LL 
    movq $.LC5, (%rsp)       "ghi" 
    movl $5678, %r9d       5678LL 
    movl $.LC0, %r8d       "def" 
    movl $1234, %ecx       1234LL 
    movl $.LC1, %edx       "abc" 
    movl $100, %esi       100 
    movl $0, %eax 
    call str_concat 
    movl $1, %eax 
    leave 
    ret 

주의 모든 스택 변위는 8 바이트 ...입니다

 
Compiler treats 0 as it was a 32-bit data-type. 
Although it does the correct displacement on the 
stack pointer, the value pushed should not be 32-bit ! 
,

내가 VS2008과 동일한 시험을 한 어셈블리 출력은 다음과 같다 :

mov QWORD PTR [rsp+112], 0 
lea rax, OFFSET FLAT:$SG3597 
mov QWORD PTR [rsp+104], rax 
mov QWORD PTR [rsp+96], 0 
mov QWORD PTR [rsp+88], 3456  ; 00000d80H 
lea rax, OFFSET FLAT:$SG3598 
mov QWORD PTR [rsp+80], rax 
mov QWORD PTR [rsp+72], 6789  ; 00001a85H 
lea rax, OFFSET FLAT:$SG3599 
mov QWORD PTR [rsp+64], rax 
mov QWORD PTR [rsp+56], 2345  ; 00000929H 
lea rax, OFFSET FLAT:$SG3600 
mov QWORD PTR [rsp+48], rax 
mov QWORD PTR [rsp+40], 5678  ; 0000162eH 
lea rax, OFFSET FLAT:$SG3601 
mov QWORD PTR [rsp+32], rax 
mov r9d, 1234    ; 000004d2H 
lea r8, OFFSET FLAT:$SG3602 
mov edx, 100    ; 00000064H 
lea rcx, QWORD PTR buf$[rsp] 
call [email protected]@YAXPEADHZZ   ; str_concat 

이번에 컴파일러는 다른 코드를 생성하고, 그것이 64 비트 데이터 타입 0으로 간주합니다 (QWORD 키워드 통지). 값과 스택 변위가 모두 정확합니다. VS와 GCC는 다르게 동작합니다.

+0

실제로 나는 방금 침몰했다. 64 비트 플랫폼에서'int' 4 바이트가 맞습니까? –

+1

시스템/컴파일러/ABI에 따라 다르지만 일반적으로 int는 4 바이트이지만 0은 아닙니다. 0은 숫자이며 int/long/char와 같은 형식이 아니며 컴파일러가 생각하는대로 승격됩니다. – Malkocoglu

+0

0은 * 8 *이 아니기 때문에 Downvoted입니다. Gentoo AMD64 초창기에 Gnome 소프트웨어를 사용하여이 문제에 부딪혔습니다. 가변 인수 목록에서 C는 0을 포인터로 변환 할 것을 모르기 때문에 void *로 형변환해야합니다. –

3

__GNUG__ 케이스를 분리하고, 두 번째 파일에 IFDEF/ENDIF 반전 두 파일을 수행

가 아니라 NULL 정의라고한다
#undef NULL 
#if defined(__cplusplus) 
#define NULL 0 
#else 
#define NULL ((void *)0) 
#endif 

((공극 *) 0) C 컴파일 용 C++의 경우 0입니다.

간단한 대답은 "C++로 컴파일하지 마십시오"입니다.

실제 문제는 가변적 인 작문 목록에 NULL을 사용하고 컴파일러의 예측할 수없는 인수 크기와 결합하여 원하는 것입니다. 네가 시도 할 수도있는 것은 "(void *) 0"대신에 NULL을 써서 목록을 종료하고 컴파일러가 4 바이트 정수 대신 8 바이트 포인터를 전달하도록한다.

+1

전달 된 'const char *'값 목록이므로 '(char *) 0 '대신'(void *) 0 '인데, 최종 결과를 구별 할 수 없다는 것에 동의한다. –

1

시스템 포함은 꼬인 미로이기 때문에 포함을 수정할 수 없습니다.

NULL 대신 (void *) 0 또는 (char *) 0을 사용하여 문제를 해결할 수 있습니다.

고려한 후에 NULL을 다시 정의하는 기존 아이디어를 거부합니다. 그렇게하는 것이 나쁜 일이며 많은 다른 코드를 망칠 수 있습니다.

+2

NULL을 재정의하면 질문이 제기 된 원인보다 더 큰 광기가 발생합니다. –

5

하나 개의 솔루션은 - 어쩌면 최고의하지만 확실히 매우 신뢰성이 - 함수에 명시 적으로 널 문자 포인터를 전달하는 것입니다 호출

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)0); 

나 :

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)NULL); 

이 권장 표준입니다 예를 들어, POSIX 시스템에서 execl() 함수에 대해 연습하십시오. 정확히 같은 이유로 - 가변 길이 인수 목록의 후행 인수는 일반적인 승격 (char 또는 int로 short, double로 float) 될 수 있지만 그렇지 않으면 b 전자 형식 안전.

C++ 실무자가 일반적으로 피해야하는 이유는 variable-length argument lists입니다. 그들은 타입 안전하지 않습니다.

관련 문제