Search code examples
cstringwindowscharscanf

sscanf_s doesn't return first character of string


I'm trying to find the first string (max 4 characters) in a comma-separated list of strings inside a C char-array.

I'm trying to achieve this by using sscanf_s (under Windows) and the format-control string %[^,]:

char mystring[] = "STR1,STR2";
char temp[5];

if (sscanf_s(mystring, "%[^,]", temp, 5) != 0) {
    if (strcmp(temp, "STR1") == 0) { return 0; }
    else if (strcmp(temp, "STR2") == 0) { return 1; }
    else { return -1; }
}

After calling sscanf_s the content of temp is not STR1 but \0TR1 (\0 being the ASCII-interpretation of 0). And the value -1 is returned.

Why do I get that behavior and how do I fix my code to get the right result (return of 0)?

EDIT: changed char mystring to mystring[] (I should have made sure I typed it correcly here)


Solution

  • There are multiple problems in your code:

    • mystring is defined as a char, not a string pointer.
    • the argument 5 following temp in sscanf_s() should have type rsize_t, which is the same as size_t. You should specify it as sizeof(temp).
    • you should specify the maximum number of characters to store into the destination array in the format string, to avoid the counter-intuitive behavior of sscanf_s in case of overflow.
    • sscanf_s returns 1 if it can convert and store the string. Testing != 0 will also accept EOF which is an input failure, for which the contents of temp is indeterminate.

    Here is a modified version:

    const char *mystring = "STR1,STR2";
    char temp[5];
    
    if (sscanf_s(mystring, "%4[^,]", temp, sizeof temp) == 1) {
        if (strcmp(temp, "STR1") == 0) {
            return 0;
        } else
        if (strcmp(temp, "STR2") == 0) {
            return 1; 
        } else {
            return -1;
        }
    }
    

    UPDATE: The OP uses Microsoft Visual Studio, which seems to have a non-conforming implementation of the so-called secure stream functions. Here is a citation from their documentation page:

    The sscanf_s function reads data from buffer into the location that's given by each argument. The arguments after the format string specify pointers to variables that have a type that corresponds to a type specifier in format. Unlike the less secure version sscanf, a buffer size parameter is required when you use the type field characters c, C, s, S, or string control sets that are enclosed in []. The buffer size in characters must be supplied as an additional parameter immediately after each buffer parameter that requires it. For example, if you are reading into a string, the buffer size for that string is passed as follows:

    wchar_t ws[10];
    swscanf_s(in_str, L"%9s", ws, (unsigned)_countof(ws)); // buffer size is 10, width specification is 9
    

    The buffer size includes the terminating null. A width specification field may be used to ensure that the token that's read in will fit into the buffer. If no width specification field is used, and the token read in is too big to fit in the buffer, nothing is written to that buffer.

    In the case of characters, a single character may be read as follows:

    wchar_t wc;
    swscanf_s(in_str, L"%c", &wc, 1);
    

    This example reads a single character from the input string and then stores it in a wide-character buffer. When you read multiple characters for non-null terminated strings, unsigned integers are used as the width specification and the buffer size.

    char c[4];
    sscanf_s(input, "%4c", &c, (unsigned)_countof(c)); // not null terminated
    

    This example reads a single character from the input string and then stores it in a wide-character buffer. When you read multiple characters for non-null terminated strings, unsigned integers are used as the width specification and the buffer size.

    char c[4];
    sscanf_s(input, "%4c", &c, (unsigned)_countof(c)); // not null terminated
    

    This specification is incompatible with the C Standard, that specifies the type of the width arguments to be rsize_t and type rsize_t to be the same type as size_t.

    As a conclusion, for improved portability, one should avoid using these secure functions and use the standard functions correctly, with the length prefix to prevent buffer overruns.

    You can prevent the Visual Studio warning about deprecation of sscanf by inserting this definition before including <stdio.h>:

    #ifdef _MSC_VER
    #define _CRT_SECURE_NO_WARNINGS  // let me use standard functions
    #endif