|
Перенос кода из под ИАРа на WinAVR, возникают некоторые вопросы... |
|
|
|
 |
Ответов
(60 - 74)
|
Nov 27 2008, 11:50
|

Местный
  
Группа: Участник
Сообщений: 340
Регистрация: 25-10-05
Из: Пермь, Россия
Пользователь №: 10 091

|
Цитата(aesok @ Nov 27 2008, 09:14)  The controlling expression of a switch statement shall have integer type. И что? Тип char - тоже integer type, что не мешает ему иметь размер 8 бит... Тут проблема совсем в другом: результат выражения (flags & 0xe0) всегда дает нули во всех байтах кроме младшего. Поэтому вменяемый оптимизатор должен выкинуть их вычисление из генерируемого кода. У меня так и происходит (см. выше). Почему этого не происходит у sonycman, непонятно... Цитата(sonycman @ Nov 27 2008, 14:28)  Но неужели слепое следование стандартам больших машин - это абсолютно правильно и на AVR? Неужели в вышеприведённом примере столь необходимо было следовать такому стандарту? не "стандартам больших машин", а правилам языка C. Цитата(sonycman @ Nov 27 2008, 14:28)  А может существует какая-то опция, позволяющая отключать эту фичу? Поверь, тебе это не нужно. Я слышал о компиляторах, у которых такая опция есть. Но писать программы с рассчетом на такую опцию - значит писать их не на языке C. То есть сразу теряется совместимость с любыми другими C компиляторами, у которых такой опции нет. Плюс периодически ломать голову над тем, почему результат выражения получился вот такой, а не такой, какой ожидался. Ты хочешь, чтобы у тебя (127 + 127) / 2 давало -1?  И все ради чего? Ради экономии пары процентов машинного кода?
--------------------
Всего наилучшего, Alex Mogilnikov
|
|
|
|
|
Nov 27 2008, 13:35
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Цитата(alx2 @ Nov 27 2008, 15:29)  Как ты получил такой код??? =8-( ) Скажи, пожалуйста, версию своего компилятора, с какой оптимизацией компилировался код и как определено byte. byte определён как unsigned char. Версия компилятора - 4.3.2 - последний релиз кандидат. Оптимизациия Оs. Полностью командные строки компилятора и линкера я приводил немного ранее. Странно всё это...
|
|
|
|
|
Nov 27 2008, 16:52
|
Знающий
   
Группа: Участник
Сообщений: 596
Регистрация: 26-05-06
Из: Москва
Пользователь №: 17 484

|
Цитата(Rst7 @ Nov 27 2008, 10:56)  вычисляется через 16 бит. Чтото не так. Расмотрим пример (x = 20): #define LCD_WIDTH 240 void lcdPrintText(PGM_P text, byte flags, unsigned char x, signed char y) { x = (LCD_WIDTH + x ) / 2; } Если расчеты выполняються в 16-битном виде то: x = (240 + 20) / 2 = 260 /2 = 130. Если в 8-битной то: x = (240 + 20) / 2 = 4 /2 = 2. Вы все еще хотите такую оптимизацию? Анатолий.
|
|
|
|
|
Nov 27 2008, 17:18
|
Знающий
   
Группа: Участник
Сообщений: 596
Регистрация: 26-05-06
Из: Москва
Пользователь №: 17 484

|
Цитата(sonycman @ Nov 27 2008, 20:03)  2aesok Если у меня в программе операнд имеет тип char, то это значит, что ни при каких условиях не будет достигнуто переполнение. Я вам привел пример когда оба операнда имеют тип char, и происходит переполнение. Цитата(sonycman @ Nov 27 2008, 20:03)  Следуя вашей логике, можно и операнды типа word обрабатывать, расширяя до double word... Компилятор это за вас не сделает, но если Вы не хотите получит ошибку переполнения, то Вы проанализируете исходные данные и формулы, и возжможно, да будете выполнять промежуточные расчеты с 32-битными числами, даже если операнды и результат 16-битные. Анатолий. Цитата(Rst7 @ Nov 27 2008, 20:08)  Только не надо спрашивать, как это - "в другом стиле", на форуме выложенно достаточно, чтобы оценить кривость моих рук  Компилятор тоже не может этого оценить. Но он стараеться генерировать правильный код в не зависимости от кривости рук програмиста, при этом код иногда получаеться более длинным. Анатолий.
|
|
|
|
|
Nov 27 2008, 19:49
|
Знающий
   
Группа: Участник
Сообщений: 596
Регистрация: 26-05-06
Из: Москва
Пользователь №: 17 484

|
Цитата Этот пример не имеет ничего общего с моим приложением. Как не трудно было заметить из моего примера, константа LCD_WIDTH всегда складывается с отрицательным числом. Плюс операнды имеют значения, не допускающие переполнения. Это Ваш пример? Код #define LCD_WIDTH 96 byte lcdGetStringWidth(PGM_P text);
void lcdPrintText(PGM_P text, byte flags, signed char x, signed char y) switch(flags & 0xe0) { case TXT_CENTERED: if (x < 0) { x = (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; } (LCD_WIDTH + x - lcdGetStringWidth(text)) = 96 + [-128..-1] - [0..255] = [95..-287] Это чисто попещаеться в signed char? В unsigned char? Цитата Насчёт кривости рук - что вы скажете про то, откуда берутся странные предупреждения об обязательной инициализации и так инициализированных строковых массивов, расположенных в памяти программ? Исходники GCC находяться здесь http://gcc.gnu.org/viewcvs/ Вы можете убрать мешающие Вам предупреждения сами. Анатолий.
Сообщение отредактировал aesok - Nov 27 2008, 19:54
|
|
|
|
|
Nov 27 2008, 21:52
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Цитата(aesok @ Nov 27 2008, 23:49)  Это Ваш пример? Код #define LCD_WIDTH 96 byte lcdGetStringWidth(PGM_P text);
void lcdPrintText(PGM_P text, byte flags, signed char x, signed char y) switch(flags & 0xe0) { case TXT_CENTERED: if (x < 0) { x = (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; } (LCD_WIDTH + x - lcdGetStringWidth(text)) = 96 + [-128..-1] - [0..255] = [95..-287] Это чисто попещаеться в signed char? В unsigned char? Нет. Но такие величины никогда не будут получены, так как операнды имеют значения, не допускающие переполнения. Не надо выдёргивать из контекста. Цитата Исходники GCC находяться здесь http://gcc.gnu.org/viewcvs/ Вы можете убрать мешающие Вам предупреждения сами. Как? Я, у которого в программе переполнения на каждом шагу, должен исправлять глюки "умного" компилятора, который стараеться генерировать правильный код в не зависимости от кривости рук програмиста? Что-то мне кажется, что мы ведём непродуктивный диалог. После того, как было установлено, что приведение операндов к 16-ти битам делается для того, чтобы просто удовлетворять стандарту, вы хотите сказать, что это не так? Что на самом деле этот шаг направлен на предотвращение переполнения? Цитата(alx2 @ Nov 27 2008, 15:29)  Как ты получил такой код??? =8-( ) Вот такой тестовый пример: Код int do_something(void);
void fff(char flags) { switch(flags & 0xe0) { case 0x80: do_something(); } } у меня компилируется вот в такой код: Код fff: /* prologue: function */ /* frame size = 0 */ andi r24,lo8(-32) cpi r24,lo8(-128) brne .L4 rcall do_something .L4: ret при любой -O отличной от -O0. gcc-4.3.1. Скажи, пожалуйста, версию своего компилятора, с какой оптимизацией компилировался код и как определено byte. Добрался до компа. На всякий случай повторю повнятнее: У меня WinAVR 20081118rc2 с компилятором GCC 4.3.2. Оптимизация Os плюс ещё несколько "фишек". Командная строка компилятора: Код avr-g++ -I"F:\Electronics\Projects\GNU\FanController\Headers" -Wall -g2 -gdwarf-2 -Os -fpack-struct -fshort-enums -mcall-prologues -funsigned-char -funsigned-bitfields -fno-exceptions -fno-threadsafe-statics -fno-inline-small-functions -ffunction-sections -mmcu=atmega88 -DF_CPU=10000000UL -MMD -MP -MF"Sources/main.d" -MT"Sources/main.d" -c -o"Sources/main.o" "../Sources/main.cpp" Скомпилировал твой пример, вот что получилось: Код void do_something(char b) { volatile static char a = b; }
void fff(char flags) { switch(flags & 0xe0) { case 0x80: do_something(0); case 0xc0: do_something(1); } }
int main( void ) {
fff(128); ... и листинг: Код void fff(char flags) { switch(flags & 0xe0) 15be: 90 e0 ldi r25, 0x00; 0 15c0: 80 7e andi r24, 0xE0; 224 15c2: 90 70 andi r25, 0x00; 0 15c4: 80 38 cpi r24, 0x80; 128 15c6: 91 05 cpc r25, r1 15c8: 21 f0 breq .+8 ; 0x15d2 <fff(char)+0x14> 15ca: 80 3c cpi r24, 0xC0; 192 15cc: 91 05 cpc r25, r1 15ce: 29 f4 brne .+10 ; 0x15da <fff(char)+0x1c> 15d0: 02 c0 rjmp .+4 ; 0x15d6 <fff(char)+0x18> { case 0x80: do_something(0); 15d2: 80 e0 ldi r24, 0x00; 0 15d4: e9 df rcall .-46 ; 0x15a8 <do_something(char)> case 0xc0: do_something(1); 15d6: 81 e0 ldi r24, 0x01; 1 15d8: e7 df rcall .-50 ; 0x15a8 <do_something(char)> 15da: 08 95 ret
|
|
|
|
|
Nov 27 2008, 22:22
|
Знающий
   
Группа: Участник
Сообщений: 596
Регистрация: 26-05-06
Из: Москва
Пользователь №: 17 484

|
Цитата(sonycman @ Nov 28 2008, 00:52)  Нет. Но такие величины никогда не будут получены, так как операнды имеют значения, не допускающие переполнения. Не надо выдёргивать из контекста. Причем тут я? Я человек я мог бы Вас и понять. Я Вам показываю как думает тупая железка, тоесть компилятор. Где Вы ему сказали что " операнды имеют значения, не допускающие переполнения"? Я Вам показывал вариант как это ему сказать: x = ( byte) (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; И компилятор начал вычислять выражение как 8-битное. Цитата(sonycman @ Nov 28 2008, 00:52)  Как? Я, у которого в программе переполнения на каждом шагу, должен исправлять глюки "умного" компилятора, который стараеться генерировать правильный код в не зависимости от кривости рук програмиста?  Я не говорил что у Вас кривые руки, Я говорил что компилятор должен компилировать программу написанную даже кривыми руками, при этом программа должна работать в соответствии со стандартом, а в стандарте никаких указазий о скорости выполнения программы нет. Это оставленно на осмотрение разработчика компилятора. Цитата(sonycman @ Nov 28 2008, 00:52)  После того, как было установлено, что приведение операндов к 16-ти битам делается для того, чтобы просто удовлетворять стандарту, вы хотите сказать, что это не так? Что на самом деле этот шаг направлен на предотвращение переполнения? Я попытался на примере Вам показать почему так написан стандарт. Цитата(sonycman @ Nov 28 2008, 00:52)  Добрался до компа. На всякий случай повторю повнятнее: Пока не знаю. Анатолий.
Сообщение отредактировал aesok - Nov 27 2008, 22:28
|
|
|
|
|
Nov 27 2008, 22:46
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Цитата(aesok @ Nov 28 2008, 02:22)  Я попытался на примере Вам показать почему так написан стандарт. Анатолий. Хм... то есть, разработчики стандарта таким образом подстраховали все вычисления с операндами типа char? Мне всё равно трудно понять рациональность этого хода. Потому, что ошибка с переполнением может возникнуть с любым по размеру операндом: 8, 16, 32, 64 и так далее бит. Почему для 8-ми бит сделали исключение? Цитата (LCD_WIDTH + x - lcdGetStringWidth(text)) = 96 + [-128..-1] - [0..255] = [95..-287] Зачем "правильно" считать результат на 16-ти битах, если в конце концов он будет разрушен, будучи "обрезанным" и "запиханным" в signed char? Если программист не продумал все варианты - ошибка обязательно вылезет всё равно. Да... Но всё-же, спасибо за попытку всё объяснить. Извиняюсь за некоторое раздражение в моём тоне. В любом случае, стандарты придумывают весьма умные дядьки, не чета таким, как я Порой трудно бывает понять их мотивы. Воистину, пути их неисповедимы
|
|
|
|
|
Nov 27 2008, 23:20
|
Знающий
   
Группа: Участник
Сообщений: 596
Регистрация: 26-05-06
Из: Москва
Пользователь №: 17 484

|
Цитата(sonycman @ Nov 28 2008, 01:46)  Хм... то есть, разработчики стандарта таким образом подстраховали все вычисления с операндами типа char? Мне всё равно трудно понять рациональность этого хода. Потому, что ошибка с переполнением может возникнуть с любым по размеру операндом: 8, 16, 32, 64 и так далее бит. Почему для 8-ми бит сделали исключение? На самом деле исключение сделано для int. Это как бы тип по умолчанию, разрядность int обычно равна разрядности регистров общего назначения, что гарантирует максимальную производительность для этого типа. Для AVR разрядность int больше разрядности регистра. Сделать int 8-битным нельзя, в стандарте требуется - размерность указателя равна размерности int, а 8-битный указатель это слишком мало. Если быть совсем точным то avr-gcc имеет ключ: -mint8 Use an 8-bit 'int' type Его практическая ценность его мала, avr-libc не совместима с этим ключом, и никогда не будет. Но если будете писать что нибудь маленькое, можете с ним поиграться. Цитата Зачем "правильно" считать результат на 16-ти битах, если в конце концов он будет разрушен, будучи "обрезанным" и "запиханным" в signed char? Если программист не продумал все варианты - ошибка обязательно вылезет всё равно. Потому что в вашем примере обрезание рузультата до char произойдеи уже после деления, а для того чтобы результат деления на 2 влез в char делимое может иметь значение до char * 2, и для его хранения нужен int. Цитата Порой трудно бывает понять их мотивы. Воистину, пути их неисповедимы На самом деле все очень просто, отладив несколько своих проектов Вы начнете думать как они... Анатолий.
Сообщение отредактировал aesok - Nov 27 2008, 23:26
|
|
|
|
|
Nov 28 2008, 00:34
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Цитата(aesok @ Nov 28 2008, 03:20)  Потому что в вашем примере обрезание рузультата до char произойдеи уже после деления, а для того чтобы результат деления на 2 влез в char делимое может иметь значение до char * 2, и для его хранения нужен int. Ну, не думаю, что компилер будет строить такие предположения. Исходное положение: Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; e54: e0 df rcall .-64; 0xe16 <lcdGetStringWidth(char const*)> e56: 21 2f mov r18, r17 e58: 33 27 eor r19, r19 e5a: 27 fd sbrc r18, 7 e5c: 30 95 com r19 e5e: 20 5a subi r18, 0xA0; 160 e60: 3f 4f sbci r19, 0xFF; 255 e62: a9 01 movw r20, r18 e64: 48 1b sub r20, r24 e66: 51 09 sbc r21, r1 e68: ca 01 movw r24, r20 e6a: 62 e0 ldi r22, 0x02; 2 e6c: 70 e0 ldi r23, 0x00; 0 e6e: 17 d5 rcall .+2606; 0x189e <__divmodhi4> Смотрите, я убираю деление совсем, и вот что имеем: Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)); e54: e0 df rcall .-64; 0xe16 <lcdGetStringWidth(char const*)> e56: 0a c0 rjmp .+20; 0xe6c <lcdPrintText(char const*, unsigned char, signed char, ... e6c: 10 5a subi r17, 0xA0; 160 e6e: 18 1b sub r17, r24 то есть весь код сводится к простым операциям. Заметьте - результат выражения по-прежнему будет: Код (LCD_WIDTH + x - lcdGetStringWidth(text)) = 96 + [-128..-1] - [0..255] = [95..-287] "невлезающим" в char, и "простая железка" может это просчитать как два пальца... Но не хочет То же самое будет, если вывести деление во вторую строку: Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)); e54: e0 df rcall .-64; 0xe16 <lcdGetStringWidth(char const*)> e56: 10 5a subi r17, 0xA0; 160 x /= 2; e58: 18 1b sub r17, r24 e5a: 81 2f mov r24, r17 e5c: 62 e0 ldi r22, 0x02; 2 e5e: 03 d5 rcall .+2566; 0x1866 <__divmodqi4> Первое и последнее выражения абсолютно эквивалентны по результату! Зато какие разные по размеру кода? Тут явно что-то в этом делении...  Не первый раз замечаю, что, чем проще и короче строка программы в си, тем лучше получается у компилятора код. Лучше разбивать одно длинное выражение на несколько простых. А так хочется извратиться позаковыристее, так, чтобы самому потом полчаса глаза таращить Задумывались бы чаще над этой проблемой те умные дядьки, которые всякие хитрые стандарты придумывают. Уже 21 век на дворе, а мы всё плюсик на одной строке, а минусик - только на следующей, а то ещё у железки голова закружится... Цитата(aesok @ Nov 28 2008, 03:20)  На самом деле все очень просто, отладив несколько своих проектов Вы начнете думать как они... Ну дай-то бог!  ЗЫ: Всем спокойной ночи!
|
|
|
|
|
Nov 28 2008, 01:08
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(sonycman @ Nov 28 2008, 02:34)  То же самое будет, если вывести деление во вторую строку: Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)); тем самым в процессе присваивания неявно приводя результат типа int к типу x, т.е. signed char, что и советовал вам aesok: Цитата(aesok @ Nov 28 2008, 00:22)  x = (byte) (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; Так что, как видите, никаких чудес. Цитата(sonycman @ Nov 28 2008, 02:34)  А тут деление signed char x на int 2 тоже с приведением результата. Tут компилятор смог сообразить и отбросить расширение.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Nov 29 2008, 11:51
|

Местный
  
Группа: Участник
Сообщений: 340
Регистрация: 25-10-05
Из: Пермь, Россия
Пользователь №: 10 091

|
Цитата(sonycman @ Nov 28 2008, 02:52)  Код switch(flags & 0xe0) { case 0x80: do_something(0); case 0xc0: do_something(1); } как показали эксперименты, как только в switch появляется второй case, в ассемблерном коде появляется вычисление незначащего старшего байта. Склоняюсь к выводу о недостатке оптимизатора в данном случае. Цитата(sonycman @ Nov 28 2008, 03:46)  Мне всё равно трудно понять рациональность этого хода. Потому, что ошибка с переполнением может возникнуть с любым по размеру операндом: 8, 16, 32, 64 и так далее бит. Почему для 8-ми бит сделали исключение? Зачем "правильно" считать результат на 16-ти битах, если в конце концов он будет разрушен, будучи "обрезанным" и "запиханным" в signed char? Если программист не продумал все варианты - ошибка обязательно вылезет всё равно. Я не знаю точно, из каких соображений язык C сделан именно так, но выскажу свои соображения по этом поводу. Во-первых, с последним утверждением согласен. Исходим из предположения, что кодер подумал, и ошибок в коде нет. Во-вторых, я подозреваю, что вероятность выхода результата неких вычислений за границы диапазона (-128...127) намного выше чем диапазона (-32768...32767). Поэтому кодеру очень часто пришлось бы писать явное приведение к типу int, например писать (0 + a + b) / 2 вместо (a + b) / 2. Лично мне это бы не понравилось, т.к. во-первых, больше текста - больше вероятность ошибиться, а во-вторых, ухудшается читаемость кода. Почему тип приводится к int, а не к long или long long - я думаю, это компромисс между удобством и эффективностью. Случаи, когда диапазон значений int для вычислений недостаточен - достаточно редки чтобы явное приведение к long (long) не доставляло больших неудобств. Наконец, что результат "в конце концов будет разрушен, будучи "обрезанным" и "запиханным" в signed char" - просто неверно, об этом aesok уже сказал. Цитата(sonycman @ Nov 28 2008, 05:34)  Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)) / 2; Код x = (LCD_WIDTH + x - lcdGetStringWidth(text)); x /= 2; Первое и последнее выражения абсолютно эквивалентны по результату! Не эквивалентны. В первом случае преобразование к типу char производится перед делением, во втором - после. Например при LCD_WIDTH=96, x=32 и lcdGetStringWidth(text)=0 результат этих выражений будет разным.
Сообщение отредактировал alx2 - Nov 29 2008, 11:53
--------------------
Всего наилучшего, Alex Mogilnikov
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|