2009-11-04 1 views
1

Я работаю на RHEL 5.1 64-битной платформе с использованием gcc 4.1.2.Проблема определения NULL на 64-битной системе

У меня есть функция полезности:

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

которого concats символа * принятый в VARIADIC списка (...), в то время как последний аргумент должен быть NULL для обозначения конца аргументов. В 64-битной системе NULL составляет 8 байтов.

Теперь проблема. Мое приложение включает прямо/косвенно 2 файла stddef.h.

Первый из них является /usr/include/linux/stddef.h, который определяет значение NULL следующим образом:

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

Второй является /USR/Библиотека/GCC/x86_64-RedHat-Linux/4.1.2/включить/stddef.h

#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-ым, так как она определяет NULL, как __null (8 байт), в то время как первый один определяет его как целое число 0 (4 байта).

Как предотвратить включение /usr/include/linux/stddef.h?

UPD:

  1. Компиляция линия довольно проста:

    г ++ -Wall -fmessage длина = 0 -g -pthread

  2. Многие из вас посоветовали пройти (недействительными *) 0. Это, конечно, сработает. Проблема в том, что функция используется во многих, я имею в виду много мест. Я хотел бы найти решение, которое даст мне то, что предлагает C++ стандартное обещание - NULL размером 8 байтов.

+0

Это поможет, если бы у нас была ваша строка компиляции, чтобы увидеть каталоги в пути включения. –

+0

Возможно ли, чтобы сгенерировался выход сборки? – Malkocoglu

+0

I preprocess file (gcc -E), чтобы доказать, что NULL заменяет на 0. – dimba

ответ

12

В этом случае нет проблемы с «NULL definiton». Существует проблема с тем, как вы пытаетесь использовать NULL в своем коде.

NULL не может быть переносимо переносимым в вариационные функции в C/C++ самостоятельно. Вы должны явно передать его перед передачей, т. Е. В вашем случае вам необходимо передать (const char*) NULL в качестве терминатора списка аргументов.

Ваш вопрос помечен как C++. В любом случае, независимо от размера, в C++ NULL всегда будет определяться как целое число . В C++ запрещено указывать NULL в качестве указателя. Поскольку ваша функция ожидает указатель (const char *), определение NULL не будет работать для него в коде на C++.

Для чистого кода вы можете определить свою собственную константу, как

const char* const STR_TERM = NULL; 

и использовать его в вызовах вашей функции. Но вы никогда не сможете в значительной степени использовать только NULL для этой цели. Всякий раз, когда простой NULL передается в качестве вариационного аргумента, это является грубой ошибкой переносимости, которая должна быть исправлена.

Добавлено: Ваше обновление утверждает, что «C++ standard обещает NULL из 8-байтного размера» (на 64-битной платформе, которую я предполагаю). Это просто не имеет никакого смысла. Стандарт C++ не обещает ничего подобного около NULL.

NULL предназначен для использования как rvalue. Он не имеет определенного размера и нет допустимого использования NULL, где его фактический размер может даже отдаленно иметь дело.


Цитируя ISO/IEC 14882: 1998, раздел 18.1 'Типы', пункт 4:

Макрос NULL является реализация определяется C++ нулевой константный указатель на этот международный стандарт (4,10). 180)

180) Возможные определения включают 0 и 0L, но не (недействительными *) 0.

+1

Идеально приемлема для пропуска указателя, не являющегося константой, и короче. –

+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. 

Я сделал быстрый тест на 64-битной платформе с 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; 
} 

И это сборка, генерируемый компилятором ...

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 

На этот раз компилятор генерирует другой код, и он относится к 0 в качестве 64-битового типа данных (обратите внимание на ключевое слово QWORD). И значение, и смещение стека правильны. VS и GCC ведут себя по-разному.

+0

На самом деле я просто оживился, это 'int' 4 байта на 64-битной платформе? –

+1

Это зависит от системы/компилятора/ABI, но да, как правило, int - 4 байта, но 0 - нет. 0 - это число, это не тип (например, int/long/char), и он продвигается до того, что компилятор считает, что он должен быть ... – Malkocoglu

+0

Downvoted, потому что 0 is * not * 8 bytes. Я столкнулся с этим с некоторым программным обеспечением Gnome в самые ранние дни Gentoo AMD64. В переменном списке аргументов, C не знает, чтобы преобразовать 0 в указатель, поэтому вы должны наложить на void *. –

3

Удаление __GNUG__ дела, и переворачивая IfDef/ENDIF во втором файле, как файлы, так делают:

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

Который должен сказать, что они определяют NULL как ((ничтожные *) 0) для компиляции C и 0 для C++.

Таким образом, простой ответ: «Не компилировать как C++».

Ваша настоящая проблема заключается в вашем желании использовать NULL в вашем вариационном списке в сочетании с непредсказуемым аргументом вашего компилятора. То, что вы МОЖЕТЕ попробовать, это написать «(void *) 0» вместо NULL, чтобы прервать ваш список, и заставить компилятор передать 8-байтовый указатель вместо 4-байтового int.

+1

Поскольку это список передаваемых значений const char *, кажется логичным использовать '(char *) 0 ', а не' (void *) 0 ', хотя я согласен, что конечный результат неотличим. –

1

Возможно, вы не сможете исправить включение, потому что система включает в себя извилистый лабиринт.

Вы можете исправить эту проблему, используя (void *) 0 или (char *) 0 вместо NULL.

После рассмотрения этого вопроса я отвергаю свою предыдущую идею переопределения NULL. Это было бы плохой способ сделать и может испортить много другого кода.

+2

Переопределение NULL приводит к безумию, превышающему то, что вызвало задание вопроса. –

5

Одно из решений - возможно, даже лучше, но, конечно, очень надежный - это передать явный указатель нулевого символа к вашей функции вызовов:

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

или:

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

Это стандартное рекомендуемое практика для функции execl() в системах POSIX, например, и по той же причине - трейлинг-аргументы списка аргументов переменной длины подлежат обычным акциям (char или short to int; float to double), но в противном случае b e тип сейф.

Это также почему практикующие C++ обычно избегают variable-length argument lists; они не являются безопасными для типа.