Почему ostream печатает `1` для строки, определенной как` volatile char [] `?
Рассмотрим этот (искусственный) пример:
#include <cstdio>
#include <iostream>
int main() {
volatile char test[] = "abc";
std::printf("%s\n", test);
std::cout << test << "\n";
}
Компиляция с помощью GCC и запуск дает следующий результат:
$ g++ test.cc
$ ./a.out
abc
1
Как вы можете видеть, printf
правильно печатает строку, а cout
печатает 1
. Почему запись в cout
создает 1
в этом случае?
Ответы
Ответ 1
Единственная подходящая перегрузка operator<<
заключается в том, что для bool
, поэтому массив преобразуется (с помощью указателя) в bool
, предоставляя true
, так как его адрес не равен нулю. Это выводится как 1
, если вы не используете манипулятор std::boolalpha
.
Нельзя использовать перегрузку для const char *
, которая выводит строку, или для const void *
, которая выводит значение указателя, поскольку для этих преобразований потребуется удалить квалификатор volatile
. Неявные преобразования указателей могут добавлять квалификаторы, но не могут их удалить.
Чтобы вывести строку, вам нужно отбросить квалификатор:
std::cout << const_cast<const char*>(test) << "\n";
но будьте осторожны, что это дает поведение undefined, так как массив будет доступен, как если бы он не был изменчив.
printf
- это вариационная функция старой школы, не дающая никакой безопасности типа. Спецификатор %s
позволяет интерпретировать аргумент как const char *
, что бы это ни было на самом деле.
Ответ 2
std:: basic_ostream:: operator < < имеет только перегрузку для const char*
или const void*
, которая не соответствует этот случай, так как вы не можете отбросить изменчивый квалификатор без броска, это описано в черновик проекта С++ 4.4
Квалификационные преобразования, в которых говорится:
Значение типа "указатель на cv1 T" может быть преобразовано в prvalue введите "указатель на cv2 T", если "cv2 T" больше cv-qual, чем "cv1 T".
поэтому используется версия bool
, и поскольку она не является nullptr
, результат равен true
.
Если вы удалите изменчивый квалификатор с test
, это обеспечит ожидаемый результат. В нескольких ответах рекомендуется использовать const_cast
для удаления летучих отборочных команд, но это поведение undefined. Мы можем видеть, перейдя в раздел 7.1.6.1
. Отборочные параграфы cv, в которых говорится:
Если делается попытка обратиться к объекту, определенному с помощью волатильно-квалифицированный тип с использованием glvalue с тип с энергонезависимой квалификацией, поведение программы undefined.
const_cast
в этом случае дает prvalue, но разыменовывает, что указатель дает значение l, которое будет вызывать поведение undefined.
Ответ 3
Ответ нашел здесь минимальным количеством поиска в Интернете:
Короткий ответ: cout
интерпретирует объект как bool
из-за квалификатора volatile
. Это причуда перегрузки для оператора <<
.
Длинный ответ: volatile-указатель не может быть преобразован в энергонезависимый указатель без явного приведения в действие, поэтому при вызове оператора <<
не может использоваться перегрузка char*
или void*
. Там нет волатильной квалифицированной перегрузки, а ближайшим совпадением является перегрузка bool
, поэтому ваш массив интерпретируется как логическое значение, а не адрес или строка.
Вы можете исправить это несколькими способами, но явный приведение, вероятно, вы хотели:
std::cout<< (char*)test <<std::endl;
(Лично я бы бросил на const char*
.)
Ответ 4
Это определитель volatile
, который передает его в bool
, вместо этого:
std::cout << const_cast<char*>(test) << "\n";