0

I recently came across the following function in the public SDK of an application that I work on:

virtual char* ExtentName() {return "";}

When I compile the application using Visual Studio with the /permissive- flag, the function above causes the following compilation error:

error C2440: 'return': cannot convert from 'const char [1]' to 'char *'
note: Conversion from string literal loses const qualifier (see /Zc:strictStrings)

I'm really surprised this code compiles under any circumstances, because it's converting a string literal (in this case a null-terminator) into a char*. How is that possible?

Additionally, I would like to fix this problem without causing an SDK break. This is what I consider the best solution:

virtual char* ExtentName() {return nullptr;}

The change above doesn't break the ABI, but I'm worried it could break our users' code, although I'm not sure how. Is that a possibility? Thank you for any information!

user3266738
  • 477
  • 2
  • 12

2 Answers2

1

Casting a string literal to char* without const qualification implicitly has been disallowed since C++11.

MSVC allows it anyway for backwards-compatibility because earlier versions of C++ allowed it. The /permissive- flag makes it behave standard-conform.

The first function does not return a null pointer value, it returns a valid pointer to a string literal which will be an array of char of length 1 containing only the null-terminator. Therefore the two functions are not at all equal.

The second function returns a null pointer value, which in contrast to the first function's return value, can e.g. not be indirected through.

You can technically keep the function signatures by doing an explicit const_cast:

virtual char* ExtentName() { return const_cast<char*>(""); }

That in itself has well-defined behavior, but any attempt to write to the array that the pointer returned from this function points to will cause undefined behavior without warning from the compiler.

Therefore this shouldn't be done. However, if you already compiled the original function against a previous C++ standard or permissive compiler, then the function already did exactly this and it would not introduce any new risks of UB. Writing through the returned pointer would have been UB without warning in the original code as well.

The correct thing to do is making the return value const char*, because the user is clearly not allowed to modify the pointed-to values returned by this function. It was probably a mistake not to do so from the beginning.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • You say it returns a valid pointer to a string literal, but isn't the literal allocated on the stack? If that's the case, the function returns a pointer to memory that has been deleted, correct? – user3266738 Nov 21 '19 at 22:57
  • @user3266738 No, string literals have static storage duration. They persist throughout the program execution. The function just returns a pointer to this static array. – walnut Nov 21 '19 at 22:59
  • I see! What about this: We don't take the address of the literal in the return statement, so we are not returning a pointer. Instead the string literal gets converted into a pointer implicitly, correct? – user3266738 Nov 21 '19 at 23:01
  • @user3266738 Yes, a string literal is an array of `const char` and arrays decay to pointers to the first element implicitly. – walnut Nov 21 '19 at 23:03
  • @user3266738 I have added a potential resolution to your problem to the answer, which I forgot earlier. – walnut Nov 21 '19 at 23:10
1

No, they are not the same at all. Currently the function returns a valid pointer to a c-string, presumably in all cases. If you change it to sometimes return a nullptr, any code that relied on the validity without checking breaks.

Ideally, you would return a std::string, but this requires changes of the code using that function.

Change the function to return a const char*. This will make clear that this c-string is const. This only "breaks" code that incorrectly assumes it is not const, but this is a good thing, since the c-string is const.

If the ABI can't be changed, leave it as it is.

alain
  • 11,939
  • 2
  • 31
  • 51