3

According to the Microsoft docs the API char * strerror(int errnum)

"maps errnum to an error-message string and returns a pointer to the string"

On the practice it turned out that there is one important aspect which is not covered by the description:

  • On Windows the same pointer for different values of errnum is used. It caused unexpected behavior: strerror() with different errnum in one printf() shows the same value!

This is the code which represents this behavior:

printf("---------- print separately [OK] ----------\n");
printf("strerror(1)=(%s), ptr=(%p)\n", strerror(1), strerror(1));
printf("strerror(2)=(%s), ptr=(%p)\n", strerror(2), strerror(2));
printf("---------- print together [BUG] ----------\n");
printf("strerror(1)=(%s), strerror(2)=(%s)\n", strerror(1), strerror(2));
printf("ptr_1=(%p), ptr_2=(%p)\n", strerror(1), strerror(2));
printf("---------- print together, get pointers separately [BUG] ----------\n");
const char * s1 = strerror(1);
const char * s2 = strerror(2);
printf("strerror(1)=(%s), strerror(2)=(%s)\n", s1, s2);
printf("ptr_1=(%p), ptr_2=(%p)\n", s1, s2);
printf("---------- --------------------------- ----------\n");

Output:

---------- print separately [OK] ----------
strerror(1)=(Operation not permitted), ptr=(000002756DE6BD90)
strerror(2)=(No such file or directory), ptr=(000002756DE6BD90)
---------- print together [BUG] ----------
strerror(1)=(Operation not permitted), strerror(2)=(Operation not permitted)
ptr_1=(000002756DE6BD90), ptr_2=(000002756DE6BD90)
---------- print together, get pointers separately [BUG] ----------
strerror(1)=(No such file or directory), strerror(2)=(No such file or directory)
ptr_1=(000002756DE6BD90), ptr_2=(000002756DE6BD90)
---------- --------------------------- ----------

Compiler optimization flags were not used. The same code works properly on Linux with gcc.

UPDATE: The questions are answered.

Q: Is this behavior a BUG?

  • NO.

Q: Is there another better API which works as strerror on Linux?

  • YES. strerror_s.
Alexander Samoylov
  • 2,358
  • 2
  • 25
  • 28
  • 1
    I wonder if you need to strcpy the char * out cos "The returned string must not be modified by the program, but may be overwritten by a subsequent call to the strerror function." according to https://en.cppreference.com/w/cpp/string/byte/strerror – doctorlove Mar 30 '22 at 16:03
  • 1
    That's why there's [`strerror_s()`](http://port70.net/~nsz/c/c11/n1570.html#K.3.7.4.2) in Annex K of C11 and C18, based on [TR 24731-1](https://stackoverflow.com/questions/372980/do-you-use-the-tr-24731-safe-functions) and Microsoft's ['safe functions'](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strerror-s-strerror-s-wcserror-s-wcserror-s?view=msvc-170). POSIX has the similar (but different) [`strerror_r()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror_r.html). – Jonathan Leffler Mar 30 '22 at 16:27
  • @doctorlove, well I saw first os-specific doc https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strerror-strerror-wcserror-wcserror?view=msvc-170. It turns out the general reference is more informative. – Alexander Samoylov Mar 30 '22 at 16:30

3 Answers3

4

This isn't a bug. Per the POSIX documentation for strerror:

The returned string pointer might be invalidated or the string content might be overwritten by a subsequent call to strerror()

So if you call it twice in a single statement, you shouldn't expect more than one of the values to be valid (and it would be perfectly acceptable for your code to crash or do other weird things).

The underlying reason is probably because the Microsoft C library is using a single fixed buffer for the strerror return value, so each call overwrites the previous held value. This is specifically allowed by the standard, for ease of implementation.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Thank you. It is more clear now. The unclear is just that fact, why it is made like this??The string descriptions of the error codes must be stored somewhere, most likely in some static array of strings. Therefore, instead of writing to the same pointer upon every strerror() call it would be much better just to return the corresponding element of that mentioned static array. It would be also thread-safe. errno may change after every operation, but the string descriptions are immutable, what is the sense to use a writable pointer?. – Alexander Samoylov Mar 30 '22 at 16:16
  • Note that `strerror()` is also standardized in C17, whose documentation agrees that the error message buffer may be overwritten by subsequent calls. As for *why*, we can only speculate, but possibilities include that the (locale-specific) error messages are not required to be stored as an array of strings, or that it is extra protection against the application erroneously modifying the error message strings (or at least trying to do so). – John Bollinger Mar 30 '22 at 16:28
  • 3
    @AlexanderSamoylov: It's likely that the strings are locale-sensitive. There are a lot of possible messages (especially on Windows), and they are looked up from somewhere and the relevant one is copied into a single destination buffer. Simply use `strerror_s()` on Windows. – Jonathan Leffler Mar 30 '22 at 16:30
  • I have just realized, there is still one thing which needs to be clarified. If we assume that that global pointer is written every time when strerror() is called then the last stored value should be printed and the statement like printf("%s%s", strerror(1), strerror(2)) should print "No such file or directory" twice. However it prints "Operation not permitted" twice. How does it work then?? – Alexander Samoylov Mar 31 '22 at 09:48
4

The memory returned by strerror can be modified by by subsequent calls to the function.

Instead, use strerror_s which allows you to supply a buffer to write the error string to.

char s1[200], s2[200];
strerror_s(s1, sizeof s1, 1);
strerror_s(s2, sizeof s2, 2);
dbush
  • 205,898
  • 23
  • 218
  • 273
0

With GNU C library you would get the same issue as strerror() is using a internal static buffer that is updated with the string corresponding to the error code passed as parameter. This is why strerror() is not multi-thread safe. The MT-safe version is strerror_r() which put the error string in a user allocated buffer instead of the internal static buffer.

Rachid K.
  • 4,490
  • 3
  • 11
  • 30