|
Компилятор в IAR, ньюансы обращения к переменным |
|
|
|
Nov 22 2006, 02:23
|
Местный
  
Группа: Свой
Сообщений: 255
Регистрация: 17-08-06
Из: Москва
Пользователь №: 19 646

|
Снова не пойму как IAR работает с переменными... Оптимизация вся выключена, дабы не влиять излишне. Имеем такой вот код: Код 111 tmpByte <<= 1; \ 00000464 9100.... LDS R16, tmpByte \ 00000468 0F00 LSL R16 \ 0000046A 9300.... STS tmpByte, R16 112 tmpByte |= tmp_bit; \ 0000046E 8108 LD R16, Y \ 00000470 .... LDI R30, LOW(tmpByte) \ 00000472 .... LDI R31, (tmpByte) >> 8 \ 00000474 8110 LD R17, Z \ 00000476 2B10 OR R17, R16 \ 00000478 8310 ST Z, R17 Почему компилятор, имея в регистре значение переменной, и следующую операцию с ней же, тем не менее сначала сохраняет ее, а потом загружает? Переменная не volatile. Почему в первом и втором случае обращение к одной и той же переменной делается по разному?? Как избежать такого извращенного обращения к переменным? Эквивалентный (нормальный) код: Код 114 tmpByte = (tmpByte<<1) | tmp_bit; \ 0000047A 9100.... LDS R16, tmpByte \ 0000047E 0F00 LSL R16 \ 00000480 8118 LD R17, Y \ 00000482 2B10 OR R17, R16 \ 00000484 9310.... STS tmpByte, R17 Спасибо заранее!
|
|
|
|
|
 |
Ответов
(15 - 29)
|
Dec 18 2006, 18:01
|
Участник

Группа: Участник
Сообщений: 23
Регистрация: 14-08-06
Пользователь №: 19 528

|
Или вот еще, тоже шедевр. Блин, чем больше в дизассемблере лажу - тем больше расстраиваюсь! Код ;p_Mask[0] = RxBuf[0]; 00011A 910C LD R16,X 00011C 01FC MOVW R30,R24 00011E 8300 ST Z,R16
;p_Mask[1] = RxBuf[1]; 000120 01FD MOVW R30,R26 000122 8101 LDD R16,Z+1 000124 01FC MOVW R30,R24 000126 8301 STD Z+1,R16
;p_Mask[2] = RxBuf[2]; 000128 01FD MOVW R30,R26 00012A 8102 LDD R16,Z+2 00012C 01FC MOVW R30,R24 00012E 8302 STD Z+2,R16
;p_Mask[3] = RxBuf[3]; 000130 01FD MOVW R30,R26 000132 8103 LDD R16,Z+3 000134 01FC MOVW R30,R24 000136 8303 STD Z+3,R16 Простое копирование двух массивов. Ну почему не задействовать пару свободных регистров указателей, скажем Х и Z? Нет, мы будем каждый раз грузить новые значения указателей в ОДНУ регистровую пару, теряя по 2 машинных цикла на каждой итерции. Вроде ж начало получилось хорошее, а дальше... Не понимаю! То же самое, реализованное в цикле. Еще хуже: Код ;for(i=0; i<4; i++) p_Mask[i] = RxBuf[i]; 000118 019C MOVW R18,R24 00011A 018D MOVW R16,R26 00011C E044 LDI R20,0x04
00011E 01F8 MOVW R30,R16 000120 9151 LD R21,Z+ 000122 018F MOVW R16,R30 000124 01F9 MOVW R30,R18 000126 9351 ST Z+,R21 000128 019F MOVW R18,R30 00012A 954A DEC R20 00012C F7C1 BRNE 0x11E Лучший код генерит только ICC, но вот полчить его в работающем виде и без ограничений по времени/коду пока сложно...
|
|
|
|
|
Dec 18 2006, 23:36
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Тем не менее местами просто сказка и песня!  Нарадоваться не могу. А вариант, который Вы показываете более характерен для подпрограмм или прерываний. Ещё некоторые моменты меня беспокоят. Например вход выход из прерывания при максимальной оптимизации по времени типа такого: 834 } \ ??CrossCallReturnLabel_588: \ 00000214 BE4F OUT 0x3F, R4 \ 00000216 .... RCALL ?Subroutine201 \ ??CrossCallReturnLabel_563: \ 00000218 .... RCALL ?Subroutine206 \ ??CrossCallReturnLabel_579: \ 0000021A .... RCALL ?Subroutine200 \ ??CrossCallReturnLabel_560: \ 0000021C 9049 LD R4, Y+ \ 0000021E 9518 RETI Где каждая п/п - восстановление группы регистров. У меня в настоящий момент М2560. Занято от силы 30К! Зачем так делать??? На ASMе такое даже в страшном сне не приснилось бы. Конечно не по Вашему вопросу, но конструкцию которую Вы приводите при большом размере пересылаемых данных лучше сделать так. memcpy(AdrActiveKom[i],AdrActiveKom[i+1],x); (В данном случае AdrActiveKom - это массив ссылок) Для данных например так: memcpy(&Status.Day+AdrStatus,&SlaveBuf[1],5); Я проверял. Получается значительно эффективнее по времени.
|
|
|
|
|
Dec 19 2006, 09:52
|

Adept
     
Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343

|
Цитата(Perepic @ Dec 18 2006, 21:01)  Или вот еще, тоже шедевр. Блин, чем больше в дизассемблере лажу - тем больше расстраиваюсь! Код ;p_Mask[0] = RxBuf[0]; 00011A 910C LD R16,X 00011C 01FC MOVW R30,R24
[...]
000134 01FC MOVW R30,R24 000136 8303 STD Z+3,R16 Простое копирование двух массивов. Ну почему не задействовать пару свободных регистров указателей, скажем Х и Z? Нет, мы будем каждый раз грузить новые значения указателей в ОДНУ регистровую пару, теряя по 2 машинных цикла на каждой итерции. Вроде ж начало получилось хорошее, а дальше... Не понимаю! Попробовал ради интереса. Получил другие результаты. Код: Код char buf1[16]; char buf2[16];
// -------------------------------------------------------------------------- void main(void) {
buf1[0] = buf2[0]; buf1[1] = buf2[1]; buf1[2] = buf2[2]; buf1[3] = buf2[4]; } // -------------------------------------------------------------------------- Результат: Код buf1[0] = buf2[0]; .... LDI R30, LOW(buf1) .... LDI R31, (buf1) >> 8 8900 LDD R16, Z+16 8300 ST Z, R16 buf1[1] = buf2[1]; 8901 LDD R16, Z+17 8301 STD Z+1, R16 buf1[2] = buf2[2]; 8902 LDD R16, Z+18 8302 STD Z+2, R16 buf1[3] = buf2[3]; 8903 LDD R16, Z+19 8303 STD Z+3, R16 Ага, но тут массивы объявлены в одном файле, компилятор их "кластеризовал" и обошелся эффективно одним указателем, благо размеры массивов это позволяют. Усложним компилятору задачу, объявив один из массивов внешним: Код char buf1[16]; extern char buf2[16]; Результат: Код buf1[0] = buf2[0]; .... LDI R26, LOW(buf1) .... LDI R27, (buf1) >> 8 .... LDI R30, LOW(buf2) .... LDI R31, (buf2) >> 8 8100 LD R16, Z 930D ST X+, R16 buf1[1] = buf2[1]; 8101 LDD R16, Z+1 930D ST X+, R16 buf1[2] = buf2[2]; 8102 LDD R16, Z+2 930C ST X, R16 9712 SBIW R27:R26, 2 buf1[3] = buf2[3]; 8103 LDD R16, Z+3 01FD MOVW R31:R30, R27:R26 8303 STD Z+3, R16 Он не так печален, как вашем примере. Может все-таки с оптимизацией что-то не то? Цитата(Perepic @ Dec 18 2006, 21:01)  То же самое, реализованное в цикле. Еще хуже: Код ;for(i=0; i<4; i++) p_Mask[i] = RxBuf[i]; 000118 019C MOVW R18,R24 00011A 018D MOVW R16,R26
[...]
00012A 954A DEC R20 00012C F7C1 BRNE 0x11E Лучший код генерит только ICC, но вот полчить его в работающем виде и без ограничений по времени/коду пока сложно... Код: Код char buf1[16]; extern char buf2[16];
// -------------------------------------------------------------------------- void main(void) {
for(char i = 0; i < sizeof(buf1); i++) { buf1[i] = buf2[i]; } } // -------------------------------------------------------------------------- Результат: Код void main(void) main: { 019D MOVW R19:R18, R27:R26
for(char i = 0; i < sizeof(buf1); i++) .... LDI R26, LOW(buf1) .... LDI R27, (buf1) >> 8 .... LDI R30, LOW(buf2) .... LDI R31, (buf2) >> 8 E100 LDI R16, 16 { buf1[i] = buf2[i]; ??main_0: 9111 LD R17, Z+ 931D ST X+, R17 } 950A DEC R16 F7E1 BRNE ??main_0 } 01D9 MOVW R27:R26, R19:R18 9508 RET Что может быть короче? IAR, как уже неоднократно говорилось и доказывалось, является лучшим компилятором для AVR на сегодняшний день. На втором месте с небольшим отставанием прочно находится AVR-GCC. Никакие ICC по качеству кодогенерации с этими двумя соревноваться не могут. Код, который вы показали, был характерен для IAR'овских компляторов версий 2.хх - они там упорно делали все подобные обращения в память через Z-pointer, что далеко не всегда хорошо, на что было указано и в версиях 3.хх уже ситуация изменилась к лучшему. Основная же причина сидит не в компиляторах, а в самом AVR, в котором мало (всего два) полноценных аппаратных регистра-указателя (Y и Z, X - неполноценный, у него отсутствует очень важный режим адресации со смещением). Т.е. один из указателей занят под указатель стека данных (еще один, мягко говоря, неудачный момент AVR - обо всех этих "фичах" AVR уже подробно говорилось, если интересно, поищите по форуму), остается один Z-pointer, который компилятор и пытается использовать. В данном случае удалось выйти с честью из положения, т.к. производится просто последовательное копирование. А вот если бы надо было выборочно извлекать байты из массивов, то тут X-pointer уже не порулил бы совсем и код был бы действительно печальным.
--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
|
|
|
|
|
Dec 19 2006, 11:29
|
Участник

Группа: Участник
Сообщений: 23
Регистрация: 14-08-06
Пользователь №: 19 528

|
Цитата(dxp @ Dec 19 2006, 09:52)  Код, который вы показали, был характерен для IAR'овских компляторов версий 2.хх - они там упорно делали все подобные обращения в память через Z-pointer, что далеко не всегда хорошо, на что было указано и в версиях 3.хх уже ситуация изменилась к лучшему. Основная же причина сидит не в компиляторах, а в самом AVR, в котором мало (всего два) полноценных аппаратных регистра-указателя (Y и Z, X - неполноценный, у него отсутствует очень важный режим адресации со смещением). Т.е. один из указателей занят под указатель стека данных (еще один, мягко говоря, неудачный момент AVR - обо всех этих "фичах" AVR уже подробно говорилось, если интересно, поищите по форуму), остается один Z-pointer, который компилятор и пытается использовать. В данном случае удалось выйти с честью из положения, т.к. производится просто последовательное копирование. А вот если бы надо было выборочно извлекать байты из массивов, то тут X-pointer уже не порулил бы совсем и код был бы действительно печальным. Совершенно верно, только приведенные здесь примеры относятся к компиллеру IAR 4.20a. Я сам удивляюсь, в программе много мест копирования массивов, и почти везде он все делает правильно. Копирование массивов с автоинкрементой регистров указателей. Особенно если большие массивы. Но бывают места, где оптимизатор "теряется" :-) Я ж не придумал это. И еще напрягают попытки ИАРа по два раза считывать одну и ту же ячейку памяти при выполнении кода типа Код if(!a) a ++; при отстутствующем спецификаторе volataile А в общем, ИАР мне нравится. Но это ж не значит, что он не может быть лучше ;-)
|
|
|
|
|
Dec 19 2006, 23:17
|
Местный
  
Группа: Участник
Сообщений: 416
Регистрация: 18-04-06
Из: Челябинск
Пользователь №: 16 219

|
Цитата(Perepic @ Dec 18 2006, 16:58)  Цитата(rezident @ Nov 22 2006, 02:42)  ИМХО потому что запись Код tmpByte <<= 1; на самом деле краткая и обозначает Код tmpByte = tmpByte << 1; Поскольку tmpByte является и источником-операндом и приемником результата, то компилятор вполне логично каждую команду раскладывает так, как ему предписано. А вот с включенной оптимизацией он возможно соптимизирует код к такому виду, который вы считаете нормальным. Интересно мыслите, однако! Как вам такой пример, я честно говоря до сих пор не понимаю отчего IAR 4.11a так себя ведет. Оптимизация включена на максимум, окромя "cross-call" Код UCHAR TimeOut; // глобальная переменная
__interrupt void TimerInt() { if(TimeOut) TimeOut--; }; Все это компилится в ледующий код: Код LDS R16, TimeOut TST R16 BREQ 0x392 LDI R30,0x9F LDI R31,0x02 LD R16,Z DEC R16 ST Z,R16 Сохраниение и восстановление стека я опустил. Что мы видим? Грузим переменную, проверяем ее, если условие выполняется, опять ее же грузим (ЗАЧЕМ?), причем засоряя адресные регистры, модифицируем и сохраняем. В результате имеем 3 лишних комманды + 4 для сохраниения-восстановления адресных регистров. Более чем в 2 раза, однако! Никак оптимальным такой код не назовешь. Что скажете..? Попробуйте так: Код if (TimeOut) TimeOut -= 1;
|
|
|
|
|
Dec 20 2006, 08:16
|
Участник

Группа: Участник
Сообщений: 23
Регистрация: 14-08-06
Пользователь №: 19 528

|
Цитата(_Bill @ Dec 19 2006, 23:17)  Попробуйте так: Код if (TimeOut) TimeOut -= 1; Попробовал. Никакой разницы. Да оно в общем-то и понятно. Было бы удивительно, ведь с точки зрения "С" а--; и а -= 1; абсолютно идентичны.
|
|
|
|
|
Dec 20 2006, 11:50
|
Местный
  
Группа: Участник
Сообщений: 416
Регистрация: 18-04-06
Из: Челябинск
Пользователь №: 16 219

|
Цитата(Perepic @ Dec 20 2006, 08:16)  Цитата(_Bill @ Dec 19 2006, 23:17)  Попробуйте так: Код if (TimeOut) TimeOut -= 1; Попробовал. Никакой разницы. Да оно в общем-то и понятно. Было бы удивительно, ведь с точки зрения "С" а--; и а -= 1; абсолютно идентичны. Все правильно. Только компиляторы разные семантически одинаковые конструкции иногда транслируют по-разному. Поэтому всегда имеет смысл "повертеть" такую конструкцию с тем, чтобы увидеть в каких случаях компилятор сгенерирует наиболее оптимальный код. Если, конечно, есть в этом необходимость.
|
|
|
|
|
Dec 21 2006, 02:12
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(_Bill @ Dec 20 2006, 11:50)  Все правильно. Только компиляторы разные семантически одинаковые конструкции иногда транслируют по-разному. Поэтому всегда имеет смысл "повертеть" такую конструкцию с тем, чтобы увидеть в каких случаях компилятор сгенерирует наиболее оптимальный код. Если, конечно, есть в этом необходимость. И вот на этой приятной ноте и хочется спросить, - а стоит ли применять такие методы? Полазайте по форуму и Вы найдёте массу примеров таких ухищрений. Но в такой ситуации всегда остаётся вероятность того, что при смене версии компилятора или просто немного изменив окружающие операторы Вы, в следующей трансляции опять потеряете "золотую нить". Получается программирование на СИ с элементами ASMа. В том смысле что прога привязана к кристалу, сама к себе, к компилятору. Например высказывание одного человека в форуме (примерно так) "... я групирую и увязываю связанные данные в структуры и в п/п передаю указатель на структуру... ...С массивами вообще не работаю... .... получается очень эффективно...". Я совершенно согласен с автором. Ничего сложного в этом нет. Понятно что можно обратится к элементу байтового массива Arr[i] или *(Arr[0]+i), но где у этого край??? Можно и умножение сдвигами заменить и двинутся дальше. По идее эффективность работы с массивами должна после трансляции быть такой же как и при работе с указателями. Но этого нет. При больших массивах выигрыш от встроенной функции копирования или очистки массивов (приведенных выше) в разы! Очень существено. Хотя тот же компилятор запросто мог бы применить аналогичный приём! И ещё никто не ответил мне на следующий вопрос. Почему при совершенно свободной Flash памяти и установленном максимальном уровне оптимизации по скорости компилятор генерит такое значительное колличество вспомогательных подпрограмм? Что я делаю не так и как этого избежать. Я же на них inline поставить не могу. Где это задаётся? Мой пост прошу не относить к критическим в адрес компилятора. Компилятор мне нравится и претензий у меня к нему, фактически нет. Просто хочу лучше узнать его. И проанализировать разные подходы людей к программированию. Особенно учитывая Ваш опыт. С уважением.
|
|
|
|
|
Dec 21 2006, 12:07
|
Участник

Группа: Участник
Сообщений: 23
Регистрация: 14-08-06
Пользователь №: 19 528

|
Цитата(SasaVitebsk @ Dec 21 2006, 02:12)  И ещё никто не ответил мне на следующий вопрос. Почему при совершенно свободной Flash памяти и установленном максимальном уровне оптимизации по скорости компилятор генерит такое значительное колличество вспомогательных подпрограмм? Что я делаю не так и как этого избежать. Я же на них inline поставить не могу. Где это задаётся? Речь как я понял идет о перекрестных вызоывах. Полезная штука, кстати сказать, при незначительной потери в скорости, получается выигрыш от 10% и выше по объему кода. Одинаковые куски кода заменяются на подпрограммы. На этапе отладки я обычно отключаю. Ищите галочку "cross call" в закладке C++ -> Optimization.
|
|
|
|
|
Dec 21 2006, 16:30
|
Местный
  
Группа: Участник
Сообщений: 416
Регистрация: 18-04-06
Из: Челябинск
Пользователь №: 16 219

|
Цитата(SasaVitebsk @ Dec 21 2006, 02:12)  Цитата(_Bill @ Dec 20 2006, 11:50)  Все правильно. Только компиляторы разные семантически одинаковые конструкции иногда транслируют по-разному. Поэтому всегда имеет смысл "повертеть" такую конструкцию с тем, чтобы увидеть в каких случаях компилятор сгенерирует наиболее оптимальный код. Если, конечно, есть в этом необходимость.
И вот на этой приятной ноте и хочется спросить, - а стоит ли применять такие методы? Полазайте по форуму и Вы найдёте массу примеров таких ухищрений. Но в такой ситуации всегда остаётся вероятность того, что при смене версии компилятора или просто немного изменив окружающие операторы Вы, в следующей трансляции опять потеряете "золотую нить". Получается программирование на СИ с элементами ASMа. В том смысле что прога привязана к кристалу, сама к себе, к компилятору. Тут нельзя сказать однозначно. Во-первых, мы так или иначе привязаны как контроллеру, с одной стороны, так и к инстументам для его программирования. И то, и другое нужно знать как можно лучше. Поэтому всякие эксперименты, позволяющие лучше узнать то, с чем работаешь, безусловно необходимы. Но... Есть машинно-независимые способы оптимизации программ. Они всем известны. Это, прежде всего, более оптимальный алгоритм. Далее, выбор наиболее подходящей организации данных. Это и оптимизация программ на уровне ЯВУ. Опять же, понимание того, как компилятор генерирует код для тех или иных конструкций языка, даст возможность создать более оптимальную программу. Например, использование оператора do while является более оптимальным по сравнению с оператором while. Сравнение с нулем даст более оптимальный код, чем сравнении с константой. То есть Код while (--i != 0) ... более эффективно, чем Код while (++i != CONST) ... Таких примеров можно привести сколько угодно. Однако, эффективные программы часто получаются менее понятными и читабельными, особенно для посторонних людей. И если мы в погоне за оптимальностью напишем такой код, который будет труден для понимания и сопровождения, и (возможно) для переноса на другую платформу, а выигрыш даст лишь пару-другую инструкций, или пару микросекунд, то стоит ли тратить на это время? Если же скорость работы программы или ее размер являются критичными, то может лучше написать критичные по этим параметрам модули на ассемблере? И такие модули могут быть более понятными, поскольку программист использует все возможности архитектуры процессора. Ему не надо становиться на уши, чтобы описать с помощью инструкций процессора то, чего средствами Си описать или очень трудно, или вообще невозможно. Использование же всяких ассемблерных вставок, как правило, не дает той эффективности, какую желают получить, а программа становится бесформенной, трудной для понимания и сопровождения. Короче говоря, во всем нужна мера.
|
|
|
|
|
Dec 26 2006, 11:15
|

Шаман
     
Группа: Модераторы
Сообщений: 3 064
Регистрация: 30-06-04
Из: Киев, Украина
Пользователь №: 221

|
Цитата(HARMHARM @ Dec 21 2006, 17:48)  Цитата(Perepic @ Dec 21 2006, 11:07)  Цитата(SasaVitebsk @ Dec 21 2006, 02:12)  И ещё никто не ответил мне на следующий вопрос. Почему при совершенно свободной Flash памяти и установленном максимальном уровне оптимизации по скорости компилятор генерит такое значительное колличество вспомогательных подпрограмм? Что я делаю не так и как этого избежать. Я же на них inline поставить не могу. Где это задаётся?
Речь как я понял идет о перекрестных вызоывах. Полезная штука, кстати сказать, при незначительной потери в скорости, получается выигрыш от 10% и выше по объему кода. Одинаковые куски кода заменяются на подпрограммы. На этапе отладки я обычно отключаю. Ищите галочку "cross call" в закладке C++ -> Optimization. Я могу ошибаться, но речь идет о Function Inlining. Cross Call делает как раз "обратную" операцию - выносит из функций общие блоки в отдельные вызовы. Вы действительно ошибаетесь. Речь была о том, что cross call оптимизацию предлагается отключить, чтобы компилятор не создавал много вложенных функций. При максимальном уровне оптимизации эта опция включена по умолчанию. Что касается Function Inlining, то это относится к пользовательским функциям. Если компилятор сочтёт их достаточно короткими, то он их инлайнит.
|
|
|
|
|
Dec 26 2006, 16:09
|

Йа моск ;)
     
Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610

|
Цитата(Perepic @ Dec 18 2006, 15:58)  Интересно мыслите, однако! Как вам такой пример, я честно говоря до сих пор не понимаю отчего IAR 4.11a так себя ведет. Оптимизация включена на максимум, окромя "cross-call" Код UCHAR TimeOut; // глобальная переменная
__interrupt void TimerInt() { if(TimeOut) TimeOut--; }; В принципе, я придерживаюсь мнения, что компилятору надо подсказывать, что делать. А также помнить об особеностях процессора - в данном случае не надо забывать, что у нас не аккумуляторная машина, и регистров валом. Имеет смысл написать так: Код char TimeOut; // ãëîáàëüíàÿ ïåðåìåííàÿ
__interrupt void TimerInt() { char to=TimeOut; if (to) TimeOut=--to; } В результате имеем нормальный код: Код 294 char to=TimeOut; \ 00000006 9100.... LDS R16, TimeOut 295 if (to) TimeOut=--to; \ 0000000A 2300 TST R16 \ 0000000C F019 BREQ ??TimerInt_0 \ 0000000E 950A DEC R16 \ 00000010 9300.... STS TimeOut, R16 296 }; \ ??TimerInt_0: Да, я понимаю, хотелось бы, чтобы компилятор сам это делал... Но с другой стороны, если лень-матушка, программируйте на Делфи для большого брата, вообще не надо думать...
--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
|
|
|
|
|
Dec 26 2006, 16:27
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(Rst7 @ Dec 26 2006, 15:09)  Имеет смысл написать так: Код char to=TimeOut; Если уж подсказывать, то подсказывать по полной программе: Код register char to=TimeOut;
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Dec 26 2006, 16:31
|

Йа моск ;)
     
Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610

|
Цитата(zltigo @ Dec 26 2006, 15:27)  Цитата(Rst7 @ Dec 26 2006, 15:09)  Имеет смысл написать так: Код char to=TimeOut; Если уж подсказывать, то подсказывать по полной программе: Код register char to=TimeOut; Согласен. Это уже полный портабл...
--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|