NoelC NoelC - 3 months ago 39
C Question

Conversion succeeds with _stscanf_s C library function, yet actually fails

This question comes with a built-in answer, but I wanted to share this little war story anyway on the chance it might help someone else...

Given an input of "20160708", do you see what's wrong with the following C statements? These are excerpted from some code that checks to see if a software update is available...

struct tm ParseTime;
memset(&ParseTime, 0, sizeof(ParseTime));

// Extract date information
int ScanResult = _stscanf_s(UTCDate, _T("%4i%2i%2i"),
&ParseTime.tm_year, &ParseTime.tm_mon, &ParseTime.tm_mday);

if (ScanResult != 3)
DEBUG_MESSAGE(MB_OK | MB_ICONERROR, _T("Debug: Unexpected Error"),
_T("ConvertUTCDateTime - could not scan UTCDate = '%s', ScanResult = %d"),
UTCDate, ScanResult);


We didn't either, at first. And the above code had passed system test many times.

But on July 8th it caused an exception in later assert statement without emitting the DEBUG_MESSAGE.

Debugging showed that even though ScanResult was loaded with the expected value of 3, implying 3 fields successfully converted, the ParseTime.tm_mday field was actually loaded with 0, an invalid month number!

But the 'i' format specifier indicates "integer", right? So what's the problem?

Answer

It occurred to us to look it up...

From C library documentation (https://msdn.microsoft.com/en-us/library/6ttkkkhh.aspx)

i - An integer.

Hexadecimal if the input string begins with "0x" or "0X",
octal if the string begins with "0", otherwise decimal.

Okay, "An integer". That's fine, right?

But...

Read the whole description... OCTAL?!? What we have here is a throwback to the 1970s!

That's right, the '0' character both causes the conversion to shift to octal, and goes ahead and converts to zero. Then the conversion stops on the invalid (for octal) '8' or '9' character, meaning the above code fails in the worst possible way only on the 8th and 9th of the month or in the 8th or 9th months!

It's debatable whether scanf with a "%2i" format specification should consider a conversion of "08" or "09" successful, then, but in fact it does - with the current Microsoft CRT library anyway.

The fix, of course, is to use "%4u%2u%2u" for the format specifier, which explicitly selects decimal conversion.

Moral of the story... Always read all the way to the end of the paragraph.

Either that or don't release new software on the 8th or 9th of the month, or in August or September. :-)

Comments