3

Platform: Win11, VisualStudio 2022 Code:

#include <stdio.h>
#include <string.h>

int main()
{
    char ori[5] = { 'a', 'b', '\0', 'c', 'd' };
    //char *safe = malloc(10 * sizeof(char));
    //char *unsafe = malloc(10 * sizeof(char));

    char safe[10];
    char unsafe[10];
    memset(safe, 0, 10 * sizeof(char));
    memset(unsafe, 0, 10 * sizeof(char));
    strcpy(unsafe, ori);
    strcpy_s(safe, 10, ori);
    printf("SAFE: %c\n", safe[3]);
    printf("UnSafe: %c\n", unsafe[3]);

    return 0;
}

I have two arrays, safe and unsafe. After memset, both of them are initialized as {0,0,0,0,0,0,0,0,0,0} (showing characters as integers).

After strcpy and strcpy_s, I expect to get {'a','b','\0','\0','\0','\0','\0','\0','\0','\0'} in both the safe and unsafe arrays. However, in safe, I obtained {97,98,0,-2,-2,-2,-2,-2,-2,-2} (showing characters as integers).

What does strcpy_s do, and where does the -2 come from?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Zhou Jia
  • 41
  • 4
  • 2
    `sizeof(char)` is always 1. It's better to write `memset(safe, 0, sizeof safe);` because you don't need to change the size anymore when `safe` is changed – phuclv Jun 29 '23 at 01:44
  • 2
    This looks like C, not C++. Why would you use C-style arrays, `malloc` & `strcpy` in C++? `printf` and `memset` I could in some cases tolerate, but there'd have to be a *really good* reason (here there's not). – Jesper Juhl Jun 29 '23 at 01:47
  • FYI: [std::size](https://en.cppreference.com/w/cpp/iterator/size). – Jesper Juhl Jun 29 '23 at 01:49
  • @JesperJuhl, The code is in C, and I used wrong tags. – Zhou Jia Jun 29 '23 at 02:09
  • @AndreasWenzel Done, with c and visual-c++, cause not find "visual-c" tag – Zhou Jia Jun 29 '23 at 02:12
  • 1
    @ZhouJia: The tag `visual-c++` is correct, as that is the name of the product. [Visual C++](https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B) supports both C and C++. – Andreas Wenzel Jun 29 '23 at 02:33

2 Answers2

6

The official Microsoft documentation for the function strcpy_s states the following:

The debug library versions of these functions first fill the buffer with 0xFE. To disable this behavior, use _CrtSetDebugFillThreshold.

If you convert 0xFE to a signed char, you get the value -2.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • [It's one of the useful debug values in MSVC](https://stackoverflow.com/a/370362/995714): 0xFE *Used to fill slack space in some memory buffers (unused parts of `std::string` or the user buffer passed to `fread()`)* – phuclv Jun 29 '23 at 04:44
3

Writing -2 is allowed by the C specification.

errno_t strcpy_s(char * restrict s1, rsize_t s1max, const char * restrict s2);

All elements following the terminating null character (if any) written by strcpy_s in the array of s1max characters pointed to by s1 take unspecified values when strcpy_s returns. C17dr § K.3.7.1.3 4

This applies regardless of any debug setting or compiler used.
Code should not rely on bytes past the null character to remain unchanged.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Visual Studio does not follow C11 Annex K "bounds checking interface". Nor does it follow C11. Nor does it follow standard C. – Lundin Jun 29 '23 at 13:38
  • @Lundin Even if MS claim [Support for C11 and C17 standards is available in Visual Studio 2019 version 16.8 and later.](https://learn.microsoft.com/en-us/cpp/overview/install-c17-support?view=msvc-170) is true, weak or false, OP can still code to current C standard. In this case, code should not rely on the value of bytes past the null character to be anything particular. – chux - Reinstate Monica Jun 29 '23 at 15:18
  • MS are shamelessly lying. Try the actual compiler: https://godbolt.org/z/zdWxPjKGz. – Lundin Jun 30 '23 at 06:41
  • @Lundin Interestingly `/std:c11 /Zc:__STDC__` and `/std:c17 /Zc:__STDC__` compile. `/std:c99 /Zc:__STDC__`, as expected, does not. – chux - Reinstate Monica Jun 30 '23 at 08:50