Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ostream prints `1` for a string defined as `volatile char[]`? [duplicate]

Consider this (artificial) example:

#include <cstdio>
#include <iostream>

int main() {
  volatile char test[] = "abc";
  std::printf("%s\n", test);
  std::cout << test << "\n";
}

Compiling it with GCC and running gives the following output:

$ g++ test.cc 
$ ./a.out 
abc
1

As you can see printf prints the string correctly while cout prints 1. Why does writing to cout produces 1 in this case?

like image 657
vitaut Avatar asked Jul 03 '14 13:07

vitaut


3 Answers

The only suitable overload of operator<< is that for bool, so the array is converted (via a pointer) to bool, giving true since its address is non-null. This outputs as 1 unless you use the std::boolalpha manipulator.

It can't use the overload for const char * which would output the string, or that for const void * which would output the pointer value, since those conversions would require removing the volatile qualifier. Implicit pointer conversions can add qualifiers, but can't remove them.

To output the string, you'd have to cast away the qualifier:

std::cout << const_cast<const char*>(test) << "\n";

but beware that this gives undefined behaviour since the array will be accessed as if it were not volatile.

printf is an old-school variadic function, giving no type safety. The %s specifier makes it interpret the argument as const char *, whatever it actually is.

like image 118
Mike Seymour Avatar answered Nov 14 '22 13:11

Mike Seymour


The std::basic_ostream::operator<< only has an overload for const char* or const void* which does not match in this case since you can not discard the volatile qualifier without a cast, this is covered in the draft C++ standard section 4.4 Qualification conversions which says:

A prvalue of type “pointer to cv1 T” can be converted to a prvalue of type “pointer to cv2 T” if “cv2 T” is more cv-qualified than “cv1 T”.

so it is using the bool version and since it is not a nullptr the result is true.

If you remove the volatile qualifier from test this will provide the result you expect. Several answers suggest using a const_cast to remove the volatile qualifiers but this is undefined behavior. We can see by going to section 7.1.6.1 The cv-qualifiers paragraph 6 which says:

If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.

const_cast in this case yields a prvalue but dereferencing that pointer yields an lvalue which will invoke undefined behavior.

like image 32
Shafik Yaghmour Avatar answered Nov 14 '22 13:11

Shafik Yaghmour


Answer found here by a minimal amount of web searching:

Short answer: cout is interpreting the object as a bool due to the volatile qualifier. It's a quirk of overloading for the << operator.

Long answer: A volatile pointer can't be converted to a non-volatile pointer without an explicit cast, so neither the char* nor the void* overload can be used when the << operator is called. There's no volatile qualified overload, and the closest match is the bool overload, thus your array is interpreted as a boolean value rather than an address or a string.

You can fix it a number of ways, but an explicit cast is probably what you wanted:

std::cout<< (char*)test <<std::endl;

(Personally I would cast to const char*.)

like image 2
Fred Foo Avatar answered Nov 14 '22 15:11

Fred Foo