16

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?

vitaut
  • 49,672
  • 25
  • 199
  • 336
  • 3
    `volatile char[N]` matches `bool` better than `const char *`. Actually, it doesn't match `const char *` at all. – chris Jul 03 '14 at 13:44
  • @sharth great catch, I did not even think to look for a dup. They are close enough they may make a good merge. – Shafik Yaghmour Jul 03 '14 at 17:11

4 Answers4

14

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.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 2
    Is this technically legal? Can `operator<<` optimize on the assumption that the string is not volatile? For example, it might do `strlen(s)` to decide how big a buffer to allocate, then use `strcpy` to copy to that buffer. – Raymond Chen Jul 03 '14 at 14:35
  • 2
    The answers with casts have undefined behavior: C++14[dcl.type.cv]p6 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." You can generally only interact with volatile arrays through hand-written loops over the elements. – Jeffrey Yasskin Jul 03 '14 at 16:44
  • @JeffreyYasskin I was wondering about this but `const_cast` in this case yields a `prvalue` as far as I can tell but I am have not thought about in detail yet. – Shafik Yaghmour Jul 03 '14 at 16:58
  • @JeffreyYasskin but I removed it from my answer until I can determine for sure. – Shafik Yaghmour Jul 03 '14 at 17:03
  • @RaymondChen: If the characters in the string are changing underneath the print, you're lost anyway: the end of the string could move earlier than the current point in the iteration. – Jeffrey Yasskin Jul 03 '14 at 20:44
  • 2
    @ShafikYaghmour: The `const_cast` does yield a prvalue `const char*` pointing to the first element of the volatile array ([expr.const.cast]). But that just means that the _pointer_ is an rvalue, not anything it points to. Dereferencing that pointer yields an lvalue referring to that first element ([expr.unary.op]), which gives undefined behavior. – Jeffrey Yasskin Jul 03 '14 at 20:52
  • 1
    @RaymondChen: Yes, you're right, it would give undefined behaviour. I've added a warning. – Mike Seymour Jul 04 '14 at 06:11
5

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
2

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*.)

Community
  • 1
  • 1
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
0

It's the volatile qualifier that casts it to a bool, try instead:

std::cout << const_cast<char*>(test) << "\n";
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • This answer has undefined behavior: C++14[dcl.type.cv]p6 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." – Jeffrey Yasskin Jul 03 '14 at 16:42
  • @JeffreyYasskin guess the variable's quite... `volatile`! ;) – Paul Evans Jul 03 '14 at 16:49