Типичные ошибки в СИ. — HackZona.Ru

Типичные ошибки в СИ.

Типичные ошибки в СИ.

Тип статьи:
Со старой ХакЗоны.
Источник:
В этой статье я постораюсь собрать те грабли, на которые я сама нарвалась, пока учила Си. Не повторяйте моих ошибок!

Берем строку из Flash.
Эта программа должна выводить в порт приветствие. На самом деле она выводит бред.

#include <avr/io.h>
#include <avr/pgmspace.h>

prog_char hello_str[]=«Hello AVR!»;

void puts(char * str)
{
while(*str!=0)
{
PORTB=*str++;
}
}

void main(void)
{
puts(Hello_str);
}

AVR-GCC понятия не имеет, куда показывает указатель — на память программ или на память данных. По умолчанию он честно берет данные из ОЗУ. Чтобы взять cтроку из Flash-памяти, пользуйтесь макросами из файла «pgmspace.h». Например так:

#include <avr/io.h>
#include <avr/pgmspace.h>

prog_char hello_str[]=«Hello AVR!»;

void puts(char * str)
{
while(PRG_RDB(str) != 0)
{
PORTB=PRG_RDB(str++);
}
}

void main(void)
{
puts(Hello_str);
}

Ждем битик в порту.
Эта функция должна ждать, пока битик в порту ввода-вывода станет ноликом… На самом деле программа здесь виснет.

void Wait_for_bit()
{
while( PINB & 0x01 );
}

При включеной оптимизации компилятор сначала вычисляет (PINB & 0x01) и кладет значение в регистр, а потом проверяет этот регистр. Для компилятора неочевидно, что значение PINB может измениться само по себе, а не в результате действий программы.

Решение: Делайте проверку внутри цикла или пользуйтесь макросами из файла «sfr_defs.h» (включен в «io.h»). Например так:

void Wait_for_bit()
{
while ( bit_is_set(PINB,0) );
}

Ждем флага от прерывания.
Эта функция должна ждать, пока не случится прерывание… На самом деле программа здесь виснет.

unsigned char flag;

void Wait_for_interrupt()
{
while(flag==0);
flag=0;
}

SIGNAL(SIG_OVERFLOW0)
{
flag=1;
}

Причина точно такая же. Для компилятора неочевидно, что значение переменной flag может измениться само по себе.

Решение: Объявить переменную flag с модификатором «volatile».

volatile unsigned char flag;

void Wait_for_interrupt()
{
while(flag==0);
flag=0;
}

SIGNAL(SIG_OVERFLOW0)
{
flag=1;
}

Организуем задержку
Эта функция должна просто занять время… На самом деле она выполняется очень быстро.

void Big_Delay()
{
long i;
for(i=0;i<1000000;i++);
}

Причина в слишком умной оптимизации AVR-GCC. Для компилятора очевидно, что функция ничего не вычисляет — она ничего не возвращает и не изменяет глобальных и статических переменных. Такую функцию можно «заоптимизировать» до нуля. (Но компилятор не так жесток — он оставляет несколько итераций цикла для целей отладки).

Решение: Пользуйтесь макросами из файла «delay.h» или впишите в тело цикла что-нибудь на ассемблере, чтобы сбить компилятор с толку.

#define nop() {asm(«nop»);}
void Big_Delay()
{
long i;
for(i=0;i<1000000;i++) nop();
}

Продолжение следует...

Удачи! =))
Нравится
Не нравится

5 комментариев

20:59
Откуда такие глубокие познание работы с памятью?
может ссылку или книжку подскажеш?????
14:41
Похоже детка балуется микроконтроллерами. Ошиблась адресом.
15:43
Самая незаменимая статья для хакера!
16:21
Видать на доп. курсы ходила.. Вообще статья прикольная...
23:06
Для того, чтобы компилятор знал, что переменная (напр. битик в порте) может изменяться извне можно просто указать модификатор доступа volatile. Или я не прав ? Кста, битик можно вылавливать не по маске (особенно для порта, где их может меняться несколько), а создав структуру битовых полей.