При просмотре старого фрагмента кода я наткнулся на некоторый ужас кодирования, подобный этому:
struct Foo
{
unsigned int bar;
unsigned char qux;
unsigned char xyz;
unsigned int etc;
};
void horror(const char* s1, const char* s2, const char* s3, const char* s4, Foo* foo)
{
sscanf(s1, "%u", &(foo->bar));
sscanf(s2, "%u", (unsigned int*) &(foo->qux));
sscanf(s3, "%u", (unsigned int*) &(foo->xyz));
sscanf(s4, "%u", &(foo->etc));
}
Итак, что же на самом деле происходит во втором и третьем sscanf
с передаваемым аргументом unsigned char*
приведением к unsigned int*
, но с указателем формата для целого числа без знака? Все, что происходит, происходит из-за неопределенного поведения, но почему это даже "работает"?
Насколько я знаю, приведение фактически ничего не делает в этом случае (фактический тип аргументов, передаваемых как ...
, неизвестен вызываемой функции). Однако это производилось годами, и оно никогда не падало, и окружающие значения, очевидно, не перезаписывались, я полагаю, потому что все члены структуры выровнены по 32 битам. Это даже чтение правильного значения на целевой машине (32-битное ARM с прямым порядком байтов), но я думаю, что оно больше не будет работать с другим порядком байтов.
Бонусный вопрос: какой самый чистый и правильный способ сделать это? Я знаю, что теперь у нас есть спецификатор формата %hhu
(очевидно, представленный в C ++ 11), но как насчет устаревшего компилятора C89?
Обратите внимание, что в исходном вопросе uint32_t
вместо unsigned int
и unsigned char
вместо uint8_t
, но это просто вводило в заблуждение и не по теме, и, кстати, исходный код, который я просматривал, использует свой собственный Определения типов.