Почему С++ позволяет мне присваивать const char константе const char *?!

К моему удивлению, это компилируется:

const char* c_str()
{
    static const char nullchar = '\0';
    return nullchar;
}

и он ввел ошибку в моем коде. К счастью, я поймал это.

Является ли это преднамеренным С++ или ошибка компилятора? Есть ли причина, по которой тип данных активно игнорируется?
Он работал в Visual С++ 2010 и GCC, но я не понимаю, почему он должен работать, учитывая очевидное несоответствие типа данных. (static также не требуется.)

Ответ 1

Как вы определили его, nullchar представляет собой целочисленное константное выражение со значением 0.

Стандарт С++ 03 определяет константу нулевого указателя как: "Константа нулевого указателя является интегральным постоянным выражением (5.19) rvalue целочисленного типа, который оценивается как ноль ". Короче говоря, ваш nullchar является константой нулевого указателя, то есть он может быть неявно преобразован и назначен практически любому указателю.

Обратите внимание, что все эти элементы необходимы для неявного преобразования в работу. Например, если вы использовали '\1' вместо '\0', или если вы не указали квалификатор const для nullchar, вы не получили бы неявное преобразование - ваше назначение потерпело бы неудачу.

Включение этого преобразования является преднамеренным, но широко известным как нежелательное. 0 как константа нулевого указателя, унаследована от C. Я довольно уверен, что Бьярн и большинство остальных членов комитета по стандарту С++ (и большинство из сообщества С++ в целом) очень хотели бы удалить это конкретное неявное преобразование, но при этом будет уничтожать совместимость с большим количеством кода C (возможно, близким к нему).

Ответ 2

Это старая история: она возвращается к C.

Нет ключевого слова null в C. Константа нулевого указателя в C:

  • интегральное постоянное выражение со значением 0, например 0, 0L, '\0' (помните, что char является интегральным типом), (2-4/2)
  • такое выражение, отличное от void*, например (void*)0, (void*)0L, (void*)'\0', (void*)(2-4/2)

Маска null (а не ключевое слово!) расширяется до такой константы нулевого указателя.

В первой конструкции С++ только константное выражение константы было разрешено как константа нулевого указателя. Недавно std::nullptr_t был добавлен в С++.

В С++, но не в C, переменная a const интегрального типа, инициализированная интегральным постоянным выражением, является интегральным постоянным выражением:

const int c = 3;
int i;

switch(i) {
case c: // valid C++
// but invalid C!
}

Итак, const char, инициализированный выражением '\0', является константой нулевого указателя:

int zero() { return 0; }

void foo() {
    const char k0 = '\0',
               k1 = 1,
               c = zero();
    int *pi;

    pi = k0; // OK (constant expression, value 0)
    pi = k1; // error (value 1)
    pi = c; // error (not a constant expression)
}

И вы думаете, что это не звуковой дизайн?


Обновлено, чтобы включить соответствующие части стандарта C99... Согласно §6.6.6...

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

Некоторые разъяснения для С++ - только программисты:

  • C использует термин "константа" для того, что программисты на С++ знают как "литерал".
  • В С++ sizeof всегда является постоянной времени компиляции; но C имеет массивы переменной длины, поэтому sizeof иногда не является постоянной времени компиляции.

Затем мы видим, что в §6.3.2.3.3 состояния...

Целочисленное константное выражение со значением 0 или такое выражение, отлитое для типа void *, называется константой нулевого указателя. Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указателем на любой объект или функцию.


Чтобы узнать, сколько лет эта функциональность, см. идентичные зеркальные части в C99 standard...

§6.6.6

Целочисленное константное выражение должно иметь целочисленный тип и должно иметь только операнды, которые являются целыми константами, константами перечисления, символьными константами, выражениями sizeof, результаты которых являются целыми константами, и плавающие константы, которые являются непосредственные операнды отливок. Операторы Cast в целочисленном постоянном выражении должны преобразовывать только арифметические типы в целые типы, за исключением того, что они являются частью операнда для оператора sizeof.

§6.3.2.3.3

Целочисленное константное выражение со значением 0 или такое выражение, отлитое для типа void *, называется константой нулевого указателя. Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указателем на любой объект или функцию.

Ответ 3

nullchar - выражение константы (compile-time-) со значением 0. Таким образом, это справедливая игра для неявного преобразования в нулевой указатель.

Более подробно: здесь я цитирую черновик проекта.

char является интегральным типом. nullchar является константой, поэтому это интегральное постоянное выражение (время компиляции) в соответствии с разделом 5.19.1:

5.19 Константные выражения [expr.const]

1 В нескольких местах на С++ требуются выражения, которые оцениваются с помощью inte-  gral или константа перечисления... Интегральное постоянное выражение может включать  ... константные переменные...

Кроме того, nullchar оценивается как 0, позволяя ему неявно преобразовывать указатель в соответствии с разделом 4.10.1:

4.10 Конверсии указателей [conv.ptr]

1 Интегральное константное выражение (expr.const) rvalue целочисленного типа  , который оценивается до нуля (называемый константой нулевого указателя), может быть  Вертикальный тип указателя.

Возможно, интуитивная причина "почему" это может быть разрешена (только с моей головы) заключается в том, что ширина указателя не указана, и поэтому допускается преобразование из постоянного интегрального выражения любого размера в нулевой указатель.


Обновлено с соответствующими частями (более нового) стандарта С++ 03... Согласно §5.19.1...

Интегральное константное выражение может включать только литералы (2.13), счетчики, переменные const или статические члены данных интегральных или перечисляемых типов, инициализированных с помощью константных выражений (8.5), несимметричных шаблонных параметров интегральных или перечисляемых типов, и sizeof.

Затем мы рассмотрим §4.10.1...

Константа нулевого указателя является интегральным постоянным выражением (5.19) rvalue целочисленного типа, который вычисляется до нуля. Константа нулевого указателя может быть преобразована в тип указателя; результатом является нулевое значение указателя этого типа и отличается от любого другого значения указателя на объект или указателя на тип функции. Два значения нулевого указателя одного и того же типа сравниваются равными.

Ответ 4

Он компилируется по той же причине, что и компиляция

const char *p = 0; // OK

const int i = 0;
double *q = i; // OK

const short s = 0;
long *r = s; // OK

Выражения справа имеют тип int и short, а инициализируемый объект - указатель. Вас это удивляет?

В языке С++ (как и в C) интегральные константные выражения (ICE) со значением 0 имеют особый статус (хотя ICE определены по-разному на C и С++). Они квалифицируются как константы нулевого указателя. Когда они используются в контекстах указателей, они неявно преобразуются в нулевые указатели соответствующего типа.

Тип char является интегральным типом, не сильно отличающимся от int в этом контексте, поэтому объект const char, инициализированный 0, также является константой нулевого указателя в С++ (но не в C).

BTW, тип bool в С++ также является интегральным типом, что означает, что объект const bool, инициализированный false, также является константой нулевого указателя

const bool b = false;
float *t = b; // OK

Ответ 5

Он не игнорирует тип данных. Это не ошибка. Он использует преимущества const, которые вы там вложили, и считая, что его значение на самом деле является целым числом 0 (char является целым типом).

Целое число 0 является действительной (по определению) константой нулевого указателя, которая может быть преобразована в тип указателя (становится нулевым указателем).

Причины, по которым вам нужен нулевой указатель, должны иметь некоторое значение указателя, которое "указывает на никуда" и может быть проверено (т.е. вы можете сравнить нулевой указатель на целое число 0, и вы вернетесь взамен).

Если вы отпустите const, вы получите сообщение об ошибке. Если вы поместили туда double (как и во многих других не целочисленных типах, я думаю, что исключения - это только типы, которые могут быть преобразованы в const char * [через перегрузку операторов преобразования]), вы получите ошибку (даже w/o const). И так далее.

Все дело в том, что в этом случае ваша реализация видит, что вы возвращаете константу null ptr; который вы можете преобразовать в тип указателя.

Ответ 6

Похоже, что в ответах на этот вопрос много ответа на этот вопрос. Подводя итог:

  • Стандарт С++ позволяет считать переменные типа t20 интегрального типа "интегральными постоянными выражениями". Зачем? Вполне возможно, чтобы обойти проблему, что C позволяет только макросам и перечислениям удерживать место интегрального постоянного выражения.

  • Возвращаясь (как минимум) к C89, интегральное постоянное выражение со значением 0 неявно конвертируется в (любой тип) нулевой указатель. И это часто используется в коде C, где NULL довольно часто #define 'd как (void*)0.

  • Возвращаясь к K & R, для представления нулевых указателей использовалось буквальное значение 0. Это соглашение используется повсеместно с таким кодом, как:

    if ((ptr=malloc(...)) {...} else {/* error */}
    

Ответ 7

есть автопривод. если вы хорошо запускаете эту программу:

#include <stdio.h>
const char* c_str()
{
    static const char nullchar = '\0';
    return nullchar;
}

int main()
{
    printf("%d" , sizeof(c_str()));
    return 0;
}

удаленный вывод должен быть 4 на моем компьютере → размер указателя.

автовыбор компилятора. уведомление, по крайней мере gcc дает предупреждение (я не знаю о VS)

Ответ 8

Я думаю, что это может быть тот факт, что нулевой символ является общим для типов. То, что вы делаете, это установить нулевой указатель, когда вы возвращаете нулевой символ. Это не сработает, если какой-либо другой символ использовался, потому что вы не передаете адрес символа указателю, а значение символа. Null - действительный указатель и значение символа, поэтому нулевой символ может быть установлен как указатель.

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