Программирование ARM: решение проблем, FAQ Какие я часто делаю ошибки в языке C Fri, January 20 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Какие я часто делаю ошибки в языке C Печать
Добавил(а) microsin   

Сколько можно наступать на одни и те же грабли?.. Пора их положить на полку, на самое видное место.

1. Неправильно меняю типизированный указатель. При изменении (например, инкрементировании) указателя забываю, что у него есть тип, и от размера этого типа зависит результат - какой адрес получится. Например, при вычислении абсолютного адреса почтового ящика CAN я написал:

AT91PS_CAN_MB CAN_Mailbox = AT91C_BASE_CAN0_MB0 + 0x20 * numMailbox;

В результате если, например, numMailbox==2, то получаю ошибочный адрес 0xFFFD0A00 вместо правильного 0xFFFD0240, так к AT91C_BASE_CAN0_MB0 прибавится не 0x20*2, как я хотел, а 0x20 * (sizeof(AT91PS_CAN_MB)*2).

Правильно надо было написать:

AT91PS_CAN_MB CAN_Mailbox = AT91C_BASE_CAN0_MB0 + numMailbox;

После такого вычисления получится правильный адрес 0xFFFD0240, так как к AT91C_BASE_CAN0_MB0 прибавится numMailbox размеров AT91PS_CAN_MB, т. е. в нашем примере sizeof(AT91PS_CAN_MB)*2 или 0x20*2.

2. Warning[Pa082]: undefined behavior: the order of volatile accesses is ...\at91lib\peripherals\can\can.c 117

Такое предупреждение возникает, если в выражении используется две переменные с атрибутом volatile, например (тут в правой стороне выражения регистры интерфейса CAN, которые имеют атрибут volatile, а слева обычная переменная u32):

canstatus = (AT91C_BASE_CAN0->CAN_SR) & (AT91C_BASE_CAN0->CAN_IMR);

исправить эту ошибку можно так:

canstatus  = AT91C_BASE_CAN0->CAN_SR;
canstatus &= AT91C_BASE_CAN0->CAN_IMR;

Еще пример такого же предупреждения (и в левой, и в правой части выражения переменные имеют атрибут volatile):

mailbox_in_use |= 1 << (pTransfer -> mailbox_number);

исправить эту ошибку можно так:

mailbox_in_use = pTransfer -> mailbox_number;
mailbox_in_use <<= 1;

3. ../graphics.c:100: warning: assignment discards qualifiers from pointer target type

Предупреждение возникло в AVR Studio (GCC) на следующий код:

const u8 ZXFONT [] = {...};
u8* adr;
adr = ZXFONT + (symcode-0x20)*8;

Проблема была в том, что я неправильно объявил указатель adr. Надо было объявить его, как и массив ZXFONT, с атрибутом const:

const u8* adr;

4. Забываю брать адрес (оператор &) от имени переменной, когда передаю его в функцию.

5. При портировании кода с ARM на AVR и обратно бывают ошибки с типом int: на AVR тип int двухбайтовый, а на ARM четырехбайтовый. Такие ошибки бывает трудно отловить. У меня одна такая встретилась в подпрограмме вычисления контрольной суммы. Пришлось одновременно запустить 2 отладчика, один для AVR, другой для ARM, и только тогда понял, в чем проблема.

6. Иногда вместо оператора ИЛИ (палка |) ставлю оператор отрицания (восклицательный знак !). Визуально различие малозаметно, и компилятор часто не ругается (например, ему все равно, какой оператор стоит, != или |=).

7. Иногда забываю правильно по времени установить семафор (флаг) какого-то действия. Такую ошибку бывает очень трудно найти и отловить. Семафор нужно всегда устанавливать ПЕРЕД действием, которое обозначает этот семафор, и ни в коем случае после действия. Это потому, что последовательность двух операций (действие и установка его семафора) не атомарна, и между ними может произойти прерывание, которое меняет семафор.

Поясню на примере. Есть некая аппаратура, например конечная точка интерфейса USB, которая требует, чтобы в момент процесса передачи в неё не осуществлялась запись. Для сигнализации о том, что эта конечная точка занята передачей, существует семафор, который показывает, что конечная точка занята, и писать данные в неё нельзя. Семафор сбрасывается прерыванием окончания передачи, а стартует передача и устанавливается семафор занятости в обычном коде. Основная программа по циклу постоянно мониторит семафор, и если семафор показывает, что "свободно", и если есть какие-то данные для передачи, то взводит семафор в состояние "занято", и пишет в конечную точку данные. Семафор перекидывается в состояние "свободно" в обработчике прерывания окончания передачи. Если по ошибке семафор взводить в состояние "занято" после записи в буфер, то прерывание окончания передачи, которое сбрасывает семафор в состояние "свободно", может сработать раньше выставления семафора в состояние "занято" в основной программе. В результате передача прекратится, так как семафор навсегда останется в состоянии "занято".

UPD140124

8. Забываю про скобки в макросах с параметрами. К примеру, можно задать макрос для вычисления таймаута:

#define TIMEOUT(sec) (sec*10)

Задумывался этот макрос для проверки счетчика времени, который тикает каждые 100 мс. Значение секунд (параметр sec) нужно умножить на 10, чтобы получить значение в единицах счетчика.

На первый взгляд макрос задан правильно. Но если ему передать в параметре sec значение 6+4, и ожидать в результате развертки макроса число 100 (ведь (6+4)*10 = 100), то на самом деле получим значение 46. Это происходило потому, что при раскрытии макроса получалось выражение 6+4*10, и это совсем не то, что ожидалось. Чтобы макрос отрабатывал правильно, нужно параметр sec заключить в дополнительные скобки: 

#define TIMEOUT(sec) ((sec)*10)

UPD140205

9. Делаю задержки на пустых циклах, а потом удивляюсь, почему у меня программа на разных уровнях оптимизации работает по-разному. К примеру код задержки наподобие

void DummyWait (u32 iterations)
{
   while(iterations--);
}

компилятор на уровнях оптимизации High и Medium может просто выкинуть, и никакой реальной задержки не получится. Кроме того, такой способ генерации задержки не гарантирует стабильную задержку, и впустую расходует ресурс процессора. Никогда не делайте задержек подобным образом, используйте глобальный таймер и прерывания. А если же все-таки решились на задержку в цикле, то используйте управление оптимизацией [2] на уровне модулей или функций (на функции с циклом задержки оптимизацию следует выключить).

UPD141219

10. Забываю про выравнивание по умолчанию полей струтур, из-за чего в них могут неожиданно появляться дырки. Вот, к примеру, как нужно задавать упакованную структуру заголовка файла BMP для компилятора GCC:

typedef struct __attribute__ ((__packed__))
{ u8 magic[2]; /* "магическая" сигнатура, обозначающая формат файла BMP:
                    0x42 0x4D (HEX-код, который указывает на буквы B и M).
                    Возможны следующие варианты:
                       BM - Windows 3.1x, 95, NT, ... и т. д.
                       BA - OS/2 Bitmap Array
                       CI - OS/2 Color Icon
                       CP - OS/2 Color Pointer
                       IC - OS/2 Icon
                       PT - OS/2 Pointer. */
  u32 filesz;    /* размер файла BMP в байтах */
  u16 creator1;  /* зарезервировано */
  u16 creator2;  /* зарезервировано */
  u32 offset;    /* смещение, т. е. начальный адрес, с которого можно найти данные растра. */
} bmp_header_t;

А так надо задать ту же самую структуру для IAR:

typedef __packed struct
{ u8 magic[2]; /* "магическая" сигнатура, обозначающая формат файла BMP:
                    0x42 0x4D (HEX-код, который указывает на буквы B и M).
                    Возможны следующие варианты:
                       BM - Windows 3.1x, 95, NT, ... и т. д.
                       BA - OS/2 Bitmap Array
                       CI - OS/2 Color Icon
                       CP - OS/2 Color Pointer
                       IC - OS/2 Icon
                       PT - OS/2 Pointer. */
  u32 filesz;    /* размер файла BMP в байтах */
  u16 creator1;  /* зарезервировано */
  u16 creator2;  /* зарезервировано */
  u32 offset;    /* смещение, т. е. начальный адрес, с которого можно найти данные растра. */
} bmp_header_t;

UPD151227

11. Переполнение из-за ограниченной разрядности используемых целых чисел. Вот типичный пример: вычисление количества угла поворота часовой стрелки.

#define TOPANGLE ((u16)LED_STRIP_PIXELS*256)
 
u16 anglehh;
u8 ss, mm, hh;
 
ss = BCDtoBIN(rtc.reg.ss);
mm = BCDtoBIN(rtc.reg.mm);
hh = BCDtoBIN(rtc.reg.hh & 0x1F);
 
//Вычисление текущего угла часовой стрелки
// с учетом минут и секунд:
anglehh = (u32)TOPANGLE*((hh*60+mm)*60+ss)/((u32)12*60*60);
...

И вроде бы все тут хорошо - данные из регистров микросхемы DS1307 преобразуются в двоичный формат, и подставляются в выражение для вычисления угла поворота часовой стрелки anglehh. Однако здесь вкралась ошибка - первым вычисляется значение в скобках hh*60+mm, но число hh байтовое, и в нем возможно переполнение, когда происходит умножение на количество часов, большее 4. Так что в этом месте требуется повышение разрядности, чтобы не происходило переполнения, например вот так:

anglehh = (u32)TOPANGLE*(((u32)hh*60+mm)*60+ss)/((u32)12*60*60);

[Ссылки]

1. Определения "char const *" и "const char *" - в чем разница?
2. Управление оптимизацией в IAR.

 

Комментарии  

 
0 #1 ВитГо 20.12.2012 10:19
Хорошие ошибки собраны !

странно что никто не отписался по ним, ведь в реале каждый на них напарывается и теряет уйму времени на отладку и понимание
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Top of Page