views:

324

answers:

4

C++ in MS Visual Studio 2008. Warning level 4 plus a load of extra warnings enabled as well. I'd expect this to give a warning at least, but more likely a compiler error?

Function declaration is as follows:

int printfLikeFunction(
   const int               bufferLength,
   char * const            buffer,
   const char * const      format,
   ... );

Code usage - there's a typo: although the ARRAY_SIZE of outputBuffer is passed in, outputBuffer itself isn't - surely this should not compile:

printfLikeFunction( ARRAY_SIZE( outputBuffer ), "Format: %s, %s", arg1, arg2 );

Clearly this is wrong and a mistake has been made. However the compiler should have caught it! The buffer parameter should be a char-pointer, and it's being passed a string literal which is a const char-pointer. This must be an error. (arg1 and arg2 are (possibly const) char pointers as well, so coincidentally the declaration is matched even without outputBuffer being in the correct place).

At run time, this code crashes as it attempts to write into the string literal. No surprise there, I just don't understand how it was allowed to compile.

(Though, incidentally, this is presumably why sprintf_s has the buffer and size parameters in a different order to this function - it makes such errors unequivocally fail).

+2  A: 

I seem to recall that the compiler has an option that controls how string literals are treated. By default they are treated as char * in order to not break a whole lot of existing non-const-safe code, but you can change it to treat them as const char *. This may help trigger the error you are looking for.

(later) I don't have a Microsoft compiler handy at the moment, but looking throught the reference on MSDN I can't seem to find such an option. I may be thinking of GCC, which does have such an option.

Greg Hewgill
Look at Charles' answer (http://stackoverflow.com/questions/1530330/why-how-does-this-compile/1530469#1530469). There's a deprecated conversion from any narrow string literal to `char*` which allows old code to compile. IMO that's a PITA, because it allows code like the above to compile without any error, but that's the way it is.
sbi
Interesting, thanks!
Greg Hewgill
+1  A: 

int printfLikeFunction( const int bufferLength, char * const buffer, const char * const format, ... );

the buffer parameter is specified as char* const, so it only protects the buffer address to be modified, not the buffer content.

to avoid buffer to be written into, you need to declare it as const char* const like the format.

the compiler allows writing into buffer since you declare it as char* . the const postfix modifier only prevents to reassign buffer value in your function.

see http://jriddell.org/const-in-cpp.html to view const modifier impact on variables

dweeves
I think the issue is that the OP forgot to pass the `buffer` parameter, and what should be the `format` parameter is being taken as `buffer`.
Dave Hinton
I suppose the buffer is meant to be modified, this is where the output string will be written. The issue here is that the string literal is treated as a char * whereas it should be treated as a const char *.
Luc Touraille
I the buffer is to be modified and a literal constant is passed as buffer parameter value, there will be memory corruption since the compiler will put the literal at a fixed adress in memory.
dweeves
And that's exactly what's happening, no surprise. The OP wants to know why this doesn't generate a compiler error.
Luc Touraille
As the comments say, buffer is supposed to be written to - the question is why a string literal (surely const char*) was allowed to be passed in as buffer (char*).
Andy Patrick
+1  A: 

String literals in C are const char pointers ( char*const ), not const pointers to const chars ( const char* const ).

C++ originally followed C usage, but then the ANSI C++ standard was amended at some point ( I'm not sure when ) to make them const char* const. Microsoft products traditionally have tended to value backwards compatibility with previous versions over compliance - there may be a compiler option to force the "new" behaviour, but since the examples of string literals on MSDN are all non-const, I doubt that there is.

Pete Kirkham
Actually, In C++, narrow string literals are of type `const char[]`, which is implicitly convertible to `const char*`. As Charles explained, there is, however, a deprecated conversion to `char*`.
sbi
+9  A: 

C++ has a special loophole for string literals for compatibility with pre-const C-style code. Although string literals are arrays of const char, they can be converted to a pointer to non-const char.

Paraphrasing 4.2/2 [conv.array]: a 'narrow' string literal can be converted to an rvalue of type pointer to non-const char. The conversion is only considered when there is an explicit target type (e.g. a function parameter) and not when a general lvalue to rvalue conversion is required.

This conversion is deprecated, but still available. Note that while the conversion allows the literal to be converted to a pointer to non-const char type, it would still invoke undefined behaviour to try to modify any of the characters in the string literal through this pointer.

Charles Bailey
Wow. What a crazy decision! But, yes, I've just checked the Standard and you are absolutely right. Unbelievable!
Andy Patrick
You would hope that Visual studio would issue a warning when using deprecated and crazy features of the standard.
caspin
Yes... even if "craziness" is subjective, deprecated is not, so it amazes me that this did not produce a warning.
Andy Patrick