2016-08-12 36 views
1

考虑以下代码:功能sscanf的必须分配给变量,否则奇怪的行为

#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; 
} 

当上面的代码运行时,内部if原因程序语句崩溃(去一些默认的处理程序)。

但是,当我改变两个(word_getValue和word_ getAddres)功能,以这样的:

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; 
} 

它的工作原理。此外如果虚拟i似乎解决了这个问题。但为什么它不以另一种方式工作呢?

GNU ARM工具链v4.8.3

+0

'WORD_getValueString()'和'WORD_getNameString()'定义在哪里?看起来您正在向我们展示您的代码的不同版本。 –

+0

在另一个文件中。但是他们的声明也包括在内。构建报告既没有警告也没有错误报告。 – Hairi

+0

我建议发布[最小,完整和可验证代码](http://stackoverflow.com/help/mcve) –

回答

3

这两个函数都会调用未定义的行为,因此会发生任何事情。添加一个额外的本地变量会更改目标变量的位置,从而隐藏其大小错误的影响。

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

sscanfsizeof(unsigned int)字节(大概4)存储到变量value,仅具有2个字节。

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

将存储sizeof(unsigned int)字节到可变address,它只有1个字节。

来解决这个问题,最简单的方法是解析为一个unsigned int并单独存放解析值到目的地,或者干脆返回值:

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; 
} 

您可能还需要验证,如果分析得到的值位于目标类型的范围内,但由于您将解析分别限制为4位和2位十六进制数字,因此不会发生溢出。

+0

返回类型“uint8_t”和“uint16_t”是否明确地转换返回的值?因为现在我返回'unsigned int',它在我的平台上是'uint32_t'。 – Hairi

+1

这些函数返回的'unsigned int'值分别隐式转换为'uint8_t'和'uint16_t'返回类型。转换是完全定义的,值被截断并给出它们如何由'sscanf'计算,它们在返回类型的范围内。 – chqrlie

+0

太棒了,所以你提供的例子确实做得很好。我不得不承认,我非常沮丧的是我正在调试的电路板的行为。对于我和其他C语言危险的新手来说,这是一个很好的例子,尤其是涉及内存的时候。 10x :) @chqrlie – Hairi

1

%x格式要求unsigned参数(假设它是uint32_t平台)。如果您通过uint16_tuint8_t它可能会损坏内存。在你的情况下,它会损坏堆栈并覆盖返回地址。尝试使用%4hx代替uint16_t%2hhx代替uint8_t

+1

'%4hx'假设目的地是一个'unsigned short'。尽管可能是这种情况,但它仍然存在技术风险,因为我们不知道“uint16_t”是否与'unsigned short'类型相同。 – chqrlie