Search code examples
cgdbembeddedgnugnu-arm

Function sscanf must be assigned to variable otherwise strange behavior


Consider this code:

#define TRANSLATOR_requestElectricityMeterWrite()  do{addr = word_getAddress(); value = word_getValue(); }while(0)

uint16_t value;
uint8_t addr;

bool dispatcher(void)
{
    TRANSLATOR_requestElectricityMeterWrite(); 
    return true;
} // AFTER this point (during debug) program goes to default handler

int main(void)
{
   if(dispatcher())
      continue;
      . . . .
      . . . . 
}

uint16_t word_getValue(void)
{
    uint16_t value;
    sscanf("ABCD", "%4x", (unsigned int *)&value);
    return value;
}

uint8_t word_getAddress(void)
{
    uint8_t address;
    sscanf("00", "%2x", (unsigned int *)&address);
        ;
    return address;
}

When the code above is run, the statement inside if causes program to crash(goes to some default handler).

But when I change the two(word_getValue and word_getAddres) functions to this:

uint16_t word_getValue(void)
{
    uint16_t value;
    int i = 0;i++;
    i = sscanf(WORD_getValueString(), "%4x", (unsigned int *)(&value));
    return value;
}

uint8_t word_getAddress(void)
{
    uint8_t address;
    int i = 0;i++;
    i = sscanf(WORD_getNameString(), "%2x", (unsigned int *)(&address));
    return address;
}

It works. The addition if the dummy i seems to solve that problem. But why doesn't it work the other way?

GNU ARM v4.8.3 toolchain


Solution

  • Both functions invoke undefined behavior, hence anything can happen. Adding an extra local variable changes the location of the destination variable, hiding the effect of its incorrect size.

    sscanf("ABCD", "%4x", (unsigned int *)&value);
    

    sscanf will store sizeof(unsigned int) bytes (probably 4) into variable value, which has only 2 bytes.

    sscanf(WORD_getNameString(), "%2x", (unsigned int *)(&address));
    

    Will store sizeof(unsigned int) bytes into variable address, which has only 1 byte.

    The easiest way to fix this problem is to parse into an unsigned int and store the parsed value to the destination separately, or simply return the value:

    uint16_t word_getValue(void) {
        unsigned int value;
        if (sscanf(WORD_getValueString(), "%4x", &value) == 1)
            return value;
        // could not parse a value, return some default value or error code
        return 0;
    }
    
    uint8_t word_getAddress(void) {
        unsigned int address;
        if (sscanf(WORD_getNameString(), "%2x", &address) == 1)
            return address;
        // could not parse a value, return some default value or error code
        return 0;
    }
    

    You might also want to verify if the parsed value is within range for the destination type, but since you limit the parse to respectively 4 and 2 hex digits, overflow cannot happen.