2016-08-12 1 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); 

sscanf будет хранить sizeof(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_t или uint8_t, это может привести к повреждению памяти. В вашем случае это повреждает стек и перезаписывает обратный адрес. Попробуйте использовать %4hx для uint16_t и %2hhx для uint8_t.

+1

'% 4hx' предполагает, что пункт назначения является« unsigned short ». Хотя это, вероятно, так и есть, это по-прежнему технически рискованно, поскольку мы не знаем, является ли 'uint16_t' тем же типом, что и' unsigned short'. – chqrlie

Смежные вопросы