Почему 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";