Чи можна використовувати NULL як заміну для значення 0?


73

Чи можна використовувати NULLвказівник як заміну для значення 0?

Або щось не так у цьому робити?


Наприклад, наприклад:

int i = NULL;

як заміна для:

int i = 0;

Як експеримент я склав наступний код:

#include <stdio.h>

int main(void)
{
    int i = NULL;
    printf("%d",i);

    return 0;
}

Вихід:

0

Справді, це дає мені це застереження, яке є власне правильним:

warning: initialization makes integer from pointer without a cast [-Wint-conversion] 

але результат все одно рівноцінний.


  • Чи я перетинаю це з "Невизначеною поведінкою"?
  • Чи допустимо використовувати NULLу такий спосіб?
  • Чи є щось не так у використанні NULLяк числового значення в арифметичних виразах?
  • І який результат та поведінка в C ++ для цього випадку?

Я прочитав відповіді: Яка різниця між NULL, '\ 0' і 0 про те, у чому різниця між NULL, \0і що 0є, але я не отримав звідти короткої інформації, якщо це цілком допустимо, а також правильно використовувати NULLяк значення, з яким потрібно працювати в завданнях та інших арифметичних операціях.


Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Samuel Liew

Справді було б краще задати два окремі запитання, одне для С та одне для С ++.
Конрад Рудольф

Відповіді:


82

Чи дозволено мені використовувати вказівник NULL як заміну для значення 0?

Ні , це не безпечно. NULL- константа нуля-вказівника, яка може мати тип int, але яка, як правило, має тип void *(в С), або в іншому випадку не може бути безпосередньо приписана до int(у С ++> = 11). Обидві мови дозволяють перетворювати покажчики на цілі числа, але вони не передбачають здійснення таких перетворень неявно (хоча деякі компілятори пропонують це як розширення). Крім того, хоча перетворення нульового покажчика в ціле число є цілим, щоб отримати значення 0, стандарт не гарантує цього. Якщо вам потрібна константа з типом intі значенням 0, то напишіть її 0.

  • Чи можу я перейти до цього невизначеного поведінки?

Так, на будь-яку реалізацію, де NULLрозширюється на значення з типом void *або будь-який інший, який не можна призначити безпосередньо int. Стандарт не визначає поведінку вашого завдання щодо такої реалізації, але його поведінка не визначена.

  • чи допустимо таким чином працювати з NULL?

Це поганий стиль, і він зламається на деяких системах і за деяких обставин. Тому що, як здається, ви використовуєте GCC, він би порушився у вашому власному прикладі, якби ви компілювали цей -Werrorпараметр.

  • Чи є щось не так у використанні NULL як числового значення в арифметичних виразах?

Так. Чи не має гарантії взагалі числове значення. Якщо ви маєте на увазі 0, то запишіть 0, що не тільки чітко визначено, але й коротше і зрозуміліше.

  • І як результат C ++ до цього випадку?

Мова C ++ суворіший щодо конверсій, ніж C, і має різні правила NULL, але там також реалізація може забезпечити розширення. Знову ж таки, якщо ви маєте на увазі 0, то це ви повинні написати.


4
Слід зазначити, що "який, як правило, має тип void *", стосується лише C. void *Не є легальним типом для C ++ (тому що ви не можете призначити void*жоден інший тип вказівника). У C ++ 89 та C ++ 03 насправді NULL має бути тип int, але в пізніших версіях він може бути (і зазвичай є) nullptr_t.
Мартін Боннер підтримує Моніку

Ви також помиляєтесь, що перетворювач void*- intце невизначена поведінка. Це не так; це реалізація визначеної поведінки.
Мартін Боннер підтримує Моніку

@MartinBonnersupportsMonica, У тих контекстах, де C вказує, що покажчик перетворений на ціле число, результат перетворення дійсно визначений реалізацією, але це не те, про що я говорю. Це призначення вказівника на значення значення цілого числа (без явного перетворення через кастинг), що має невизначене поведінку. Мова не визначає там автоматичне перетворення.
Джон Боллінгер

@MartinBonnersupportsMonica, я змінив, щоб бути більш вражаючим C ++ міркувань. У будь-якому випадку центральна тема застосовується однаково до обох мов: якщо ви хочете ціле число 0, тоді явно запишіть його як цілу константу відповідного типу.
Джон Боллінгер

31

NULLє деякою нульовою постійною покажчиком. У C це може бути ціле постійне вираз зі значенням 0або такий вираз, поданий до void*, причому останній є більш імовірним. Це означає, що ви не можете припустити, що використовувати NULLвзаємозамінні з нулем. Наприклад, у цьому зразку коду

char const* foo = "bar"; 
foo + 0;

Заміна 0на NULLне гарантовано є дійсною програмою С, оскільки додавання між двома вказівниками (не кажучи вже про різні типи вказівників) не визначено. Це призведе до видачі діагностики через порушення обмеження. Операнди для додавання не дійсні .


Що стосується C ++, то тут дещо інакше. Відсутність неявного перетворення з void*інших типів об'єктів означало, що NULLісторично було визначено, як 0у коді C ++. У C ++ 03 ви, мабуть, зможете втекти від цього. Але оскільки C ++ 11, його юридично можна визначити як nullptrключове слово . Тепер знову створюється помилка, оскільки std::nullptr_tможе не додаватися до типів вказівника.

Якщо NULLце визначено як, nullptrтоді навіть ваш експеримент стає недійсним. Немає перетворення з std::nullptr_tцілого числа. Ось чому це вважається більш безпечною константою нульового покажчика.


Для повноти 0L також є нульовою константою вказівника і може використовуватися як NULLв обох мовах.
eerorika

1
@jamesqf Стандарт говорить, що ціла константа зі значенням 0 - це нульова константа вказівника. Тому 0L - нульова константа вказівника.
eerorika

1
@eerorika: Якраз те, що потрібно світові, стандарти, які ігнорують реальність :-) Тому що, якщо я правильно запам’ятав свою збірку 80286, ви навіть не можете призначити дальний вказівник як одну операцію, тому авторам-компіляторам доведеться спеціально -укладіть.
jamesqf

2
@jamesqf За запитаннями C , re: введення 0нульової вказівника константи: "очевидно, як супутник усім існуючим погано написаним кодом C, який зробив неправильні припущення"
Ендрю Генле

3
@jamesqf, що будь-яка ціла константа зі значенням 0 є константою з нульовим покажчиком (в С) не має нічого спільного з реалізацією апаратних покажчиків. Зауважимо також, що стандарт C не впізнає відмінності між вказівниками ближнього та далекого в будь-якому випадку, але підтримує призначення вказівника-вказівника. Він також підтримує (деякі) порівняння вказівників, які представляють цікаві проблеми для сегментованих форматів адреси, таких як 286.
Джон Боллінгер

21

Чи дозволено мені використовувати вказівник NULL як заміну для значення 0?

int i = NULL;

Правила відрізняються між мовами та їх версіями. В одних випадках ти можеш, а в інших - не можеш. Незважаючи на це, ви не повинні . Якщо вам пощастить, ваш компілятор попередить, коли ви спробуєте це, а ще краще, не вдасться компілювати.

В C ++, перед C ++ 11 (цитата з C ++ 03):

[lib.support.types]

NULL - визначена реалізацією константа нульового вказівника C ++ у цьому Міжнародному стандарті.

Мало сенсу використовувати нульову константу вказівника як ціле число. Однак ...

[conv.ptr]

Нульова константа вказівника - це ціле постійне вираження (5.19) значення цілочисельного типу, яке оцінюється на нуль.

Отже, це технічно спрацювало б навіть якщо це безглуздо. Завдяки цій технічності ви можете зіткнутися з погано написаними програмами, які зловживаютьNULL .

Оскільки C ++ 11 (цитата з останньої чернетки):

[conv.ptr]

Константа покажчика NULL являє собою ціле число буквальна ([lex.icon]) з нульовим значенням або prvalue типу станда :: nullptr_t .

A std​::​nullptr_­tне перетворюється в ціле число, тому використовуєтьсяNULL як цілого числа буде працювати лише умовно, залежно від вибору, здійсненого мовною реалізацією.

PS nullptr- первинне значення типу std​::​nullptr_­t. Якщо вам не потрібна ваша програма для компіляції в попередньому C ++ 11, ви завжди повинні використовувати її nullptrзамість NULL.


C дещо інший (цитати проекту C11 N1548):

6.3.2.3 Мова / Перетворення / Інші операнди / Покажчики

3 Ціле постійне вираз зі значенням 0, або такий вираз, переданий типуvoid * , називається нульовою постійною покажчиком. ...

Так, випадок схожий на пост C ++ 11, тобто зловживання NULLроботами, що умовно залежать від вибору, зробленого мовою реалізації.


10

Так , хоча в залежності від реалізації ви , можливо , знадобитися акторський . Але так, це на 100% законно, інакше.

Хоча це дійсно, справді, дуже поганий стиль (зайве говорити?).

NULLє, або був, насправді не C ++, це C. Це стандарт , однак, як і для багатьох С спадщини, має два пункти ([diff.null] та [support.types.nullptr]), які технічно складають NULLC ++. Це константа нуля-вказівника, визначена реалізацією . Тому, навіть якщо це поганий стиль, він технічно такий, наскільки це C ++.
Як зазначено в примітці , можливі варіанти реалізації можуть бути 0або 0L, але НЕ (void*)0 .

NULLмогло б, звичайно (стандарт прямо не говорить про це, але це майже єдиний вибір, що залишився після 0або 0L) бути nullptr. Це майже ніколи не буває, але це юридична можливість.

Попередження, яке вам показав компілятор, демонструє, що компілятор насправді не відповідає (якщо ви не компілювались у режимі C). Тому що, згідно з попередженням, він конвертував нульовий покажчик (не такий, nullptrякий би був nullptr_t, який би був виразний), так що, очевидно, визначення NULLсправді є таким (void*)0, яким воно може не бути.

У будь-якому випадку у вас є два можливих законних (тобто компілятор не порушений) випадків. Або (реалістичний випадок), NULLщось на кшталт 0або 0L, тоді у вас є "нуль або один" перетворення на ціле число, і вам добре піти.

Або NULLсправді nullptr. У цьому випадку у вас є чітке значення, яке має гарантії порівняння, а також чітко визначені перетворення з цілих чисел, але, на жаль, не до цілих чисел. Однак він має чітко визначене перетворення в bool(в результаті false) і boolмає чітко визначене перетворення в ціле число (в результаті чого 0).

На жаль, це дві конверсії, тож це не в межах "нуля чи одиниці", як зазначено в [conv]. Таким чином, якщо ваша реалізація визначається NULLяк nullptr, тоді вам доведеться додати чіткий склад, щоб ваш код був правильним.


6

Від C faq:

Питання: Якщо NULL і 0 еквівалентні нульовим константам покажчика, що я повинен використовувати?

Відповідь: Лише в контекстах покажчика це NULLі 0є рівнозначно. NULLслід НЕ використовуватися , коли потрібно інший вид 0, хоча це могло б працювати, так як це посилає неправильне стилістичне повідомлення. (Крім того, ANSI дозволяє визначити NULL ((void *)0), яке не працюватиме взагалі в не-покажчикових контекстах.) Зокрема, не використовуйте, NULLколи потрібний нульовий символ ASCII (NUL). Надайте власне визначення

http://c-faq.com/null/nullor0.html


5

Відмова: Я не знаю C ++. Моя відповідь не призначена для застосування у контексті C ++

'\0'- intзначення зі значенням нуля, саме 100% точно так само 0.

for (int k = 10; k > '\0'; k--) /* void */;
for (int k = 10; k > 0; k--) /* void */;

В контексті покажчиків , 0і NULLє 100% еквівалентні:

if (ptr) /* ... */;
if (ptr != NULL) /* ... */;
if (ptr != '\0') /* ... */;
if (ptr != 0) /* ... */;

всі 100% еквівалентні.


Примітка о ptr + NULL

Контекст ptr + NULLє НЕ що покажчики. Немає визначення для додавання покажчиків мовою С; покажчики та цілі числа можна додавати (або віднімати). У ptr + NULLразі або ptrабо NULLє покажчиком, а інший повинен бути цілим числом, так ptr + NULLефективно , (int)ptr + NULLабо ptr + (int)NULLі в залежності від термінів ptrі NULLкілька поводжень можна очікувати: все це працює, попередження для перетворення між покажчиком і ціле, відмова компілювати, .. .


Я бачив і #define NULL (void *)0раніше. Ви впевнені, що NULL і звичайний 0 є 100% еквівалентом?
machine_1

2
У контексті покажчика, так ... умова, підкреслена у моїй відповіді, дякую
pmg

@phuclv: Я абсолютно не маю уявлення про C ++. Моя відповідь (крім біта між дужками) стосується C
pmg

@phuclv ptr + NULLне використовує NULLв контексті покажчиків
pmg

3
@JesperJuhl: в контексті покажчиків вони на 100% еквівалентні. Я поняття не маю, що nullptrтаке, але ((void*)0)і 0(або '\0') еквівалентні в контексті покажчиків ...if (ptr == '\0' /* or equivalent 0, NULL */)
pmg

5

Ні, більше не NULLвважається за краще використовувати (старий спосіб ініціалізації вказівника).

Оскільки C ++ 11:

Ключове слово nullptrпозначає вказівник буквально. Це перше значення типу std :: nullptr_t. Існують неявні перетворення з nullptrнульового значення вказівника будь-якого типу вказівника та будь-якого вказівника на тип члена. Подібні перетворення існують для будь-якої константи нульового покажчика, що включає значення типу std::nullptr_t, а також макрос NULL.

https://en.cppreference.com/w/cpp/language/nullptr

На насправді, станд :: nullptr_t типу нульового покажчика буквального nullptr. Це окремий тип, який не є самим типом вказівника або вказівником на тип члена.

#include <cstddef>
#include <iostream>

void f(int* pi)
{
   std::cout << "Pointer to integer overload\n";
}

void f(double* pd)
{
   std::cout << "Pointer to double overload\n";
}

void f(std::nullptr_t nullp)
{
   std::cout << "null pointer overload\n";
}

int main()
{
    int* pi; double* pd;

    f(pi);
    f(pd);
    f(nullptr);  // would be ambiguous without void f(nullptr_t)
    // f(0);  // ambiguous call: all three functions are candidates
    // f(NULL); // ambiguous if NULL is an integral null pointer constant 
                // (as is the case in most implementations)
}

Вихід:

Pointer to integer overload
Pointer to double overload
null pointer overload

Питання полягає у призначенні NULL 0 для цілих чисел. У цьому сенсі нічого не змінюється з nullptr замість NULL.
ivan.ukr

Він використовував слова як "вказівник NULL".
Маной

До речі, C ++ не мають концепції NULL після C ++ 11. Автор може заплутатися у використанні constexpr або визначити старий спосіб ініціалізації. en.cppreference.com/w/cpp/language/default_initialization
Mannoj
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.