реклама на сайте
подробности

 
 
5 страниц V   1 2 3 > »   
Reply to this topicStart new topic
> Портируемый код - миф или реальность?
CD_Eater
сообщение Oct 6 2007, 09:03
Сообщение #1


Частый гость
**

Группа: Новичок
Сообщений: 173
Регистрация: 3-09-04
Из: Moscow
Пользователь №: 595



Очень хочется покидать камушки в огород тех, кто пишет на Си и верит в миф о лёгком портировании кода. Нет возражающих? Ну, тогда начну.

Недавно на телесисях была поучительная ветка. Вот она
http://telesys.ru/wwwboards/mcontrol/1809/...es/173406.shtml
Прочитайте её, интересно.

Прочитали? Тогда продолжим.
Это почти классический пример - ЯВУ скрыл от программиста важнейшую деталь реализации: с виду атомарная инструкция "PORTB ^= 1" на самом деле оказалась неатомарной, и автор совершенно не ожидал, что здесь придётся заботиться о корректном доступе к общему ресурсу (порту B ) в многопоточной среде (фоновая задача + обработчик прерывания). Если бы человек писал на ассемблере, он бы легко увидел эту потенциальную "борьбу за ресурс" и принял бы обычные в таких случаях меры. Не исключено (хотя маловероятно), что автор предвидел эту возможную угрозу, но понадеялся, что компилятор достаточно умный и автоматически выберет самый оптимальный вариант реализации, то есть, "PINB = 1", сделав операцию атомарной (разумеется, компилятор сделал бы это из соображений оптимизации по скорости/размеру кода, а не из заботы об атомарности). Для нас важно одно: простые и однозначные с виду операции ЯВУ могут (в зависимости от компилятора и железа) оказаться вовсе не такими простыми и однозначными.

Теперь представьте такую ситуацию. У Вас есть Сишный исходник и Вы портируете код проекта на другой МК. Разные не только МК, но и компиляторы. Предположим, что в проекте встречается ситация, аналогичная приведённой выше: в фоновой задаче и в обработчике прерывания используется нечто вроде "PORTB ^= 1", но в предыдущей реализации (при старом МК и старом компиляторе) эта операция была атомарной, а в новой реализации (на новом МК и новом компиляторе) перестала быть атомарной. Вопрос: заметите ли Вы это???
При переносе исходника Вы успешно подправите имена портов, замените где нужно номера битов этих портов, но придёт ли Вам в голову менять стандартную Сишную инструкцию "PORTB ^= 1" на какую-то другую? Да никогда в жизни! И программа успешно скомпилируется, и на Вашем столе она заработает без ошибок, и пирожок с полки Вы возьмёте, преисполненный гордостью за умение легко портировать программы. А потом, через месяц-другой, придут недобрые вести с поля - что-то там не фурычит. Приезжай, творец, разбирайся что натворил - изредка бывают сбои. Спорим, что первое, на что Вы подумаете - это на недостаточную помехозащищённость Вашего девайса. smile.gif Вероятность того, что прерывание попадёт внутрь Read-Modify-Write последовательности, достаточно мала и при тестировании обнаружена не будет, но вот в поле (помножьте эту вероятность на тысячу устройств, которые Вы уже изготовили и внедрили) уверенно даст о себе знать. Пока Вы, начиная с мыслей о помехозащищённости, дойдёте до реальной причины неисправности, пройдёт порядка месяца, не меньше, т.к. это очень труднообнаруживаемая ошибка. Вы вряд ли станете искать ошибку в софте, т.к. при портировании код фактически вообще не был изменён, а в предыдущей реализации сбоев не было. Возможно, ошибку так и не найдёте, но будете всем рассказывать сказки про плохую помехозащищённость или ужасную глючность новых МК...

Имхо:
Надеяться на лёгкую портируемость проектов с одного МК на другой - это путь в страну Грабляндию. Создавая проект, пишите его применительно к данной задаче и для данного МК. Если всё же придётся переносить на другой МК, переносите только блок-схему алгоритма, заново наращивая "скелет" деталями реализации. Чаще всего придётся даже немного изменить алгоритм работы, приспособив его к особенностям новой периферии (например, использовать не поллинг пина по таймеру, а функцию захвата и т.п.).

P.S. Сейчас меня обвинят в том, что я не люблю или не умею писать портируемые программы, что большинство программистов в мире пишут только портируемый код, и что за портируемым кодом будущее. В ответ замечу одну большую разницу - не сравнивайте программирование для PC и для МК. Легко портируются те программы, которые фактически не зависят от аппаратуры, например, математические вычисления (БПФ, фильтры и т.п.). Большинство программ для МК сильно зависят от железа. Правильный с точки зрения программирования подход к созданию легко портируемого кода состоит в том, чтобы выделять в отдельные функции (возможно, inline-функции) всё обращение к периферии, и при каждом портировании переписывать эти функции полностью. Причём для одной и той же операции, например, для "PORTB ^= 1", должны присутствовать две функции: одна реентабельная (для случаев типа нашего), другая нереентабельная, зато быстрая (для обычного однопоточного программирования). Тогда проблем будет гораздо меньше, но программы перестанут быть эффективными. Короче, стандартная ситуация с плюсами и минусами универсальности (тут очень в тему будут слова автора с ником -=Shura=- про джаву - искать гуглем по сайту телесистем фразу "anal sex")...
Go to the top of the page
 
+Quote Post
sensor_ua
сообщение Oct 6 2007, 10:10
Сообщение #2


Профессионал
*****

Группа: Свой
Сообщений: 1 266
Регистрация: 22-04-05
Из: Киев
Пользователь №: 4 387



Уважаемый,
я, конечно, понимаю Ваше настроение. Похоже, написание портируемого кода от Вас требуют по работе и аргументируют этим требованием использование инструмента, якобы избавляющего от проблем при портировании. ИМХО, во-первых нужно просто не уходить в крайности. Я бы сказал просто - ассемблер не обеспечивает портируемость априори. Это совсем не значит, что Си её обеспечивает безо всяких дополнительных мер. Существуют разные подходы к обеспечению портируемости кода, которые можно разделить (слегкаwink.gif) - по разновидности применяемого компилятора и по группированию портируемых участков кода и аппаратно/инструментально зависимых. Для многих ОС есть порты под различные компиляторы (назовём упрощенно "различные синтаксически различающиеся комплекты диалектов Си и ассемблеров"). Но, например, для исходных кодов файловых систем часто выделяются аппаратно-зависимые части - функции ввода вывода - функции чтения/записи блока данных на/с носителя.
В то же время нужно было бы напомнить о самом языке Си - в нём, в отличие от других ЯВУ, например, Паскаля, НЕТ ОПЕРАТОРОВ ВВОДА-ВЫВОДА. Это не случайно. Умные книги повторять не буду - сами почитаете, если вдруг не читали.
Указанные проблемы неатомарности некоторых операций должны быть, ИМХО, понятны без утомительного застирывания. Упрощение записи ещё не повод не понимать, что же будет выполняться
x^=y; => x = x^y;
Далее вспоминаем, что x объявлена как volatile.
Начинаем вспоминать, что значение порта ДОЛЖНО быть прочитано при любых условиях, модифицировано и уж после записано назад. Уж не помню, но в C51 чтение-модификация-запись порта вроде бы была атомарной операцией, но, даже если и так, то это скорее исключение. Итого - операция ввода-вывода, если следовать канонам Си, должна быть описана функцией. (Но мы-то сами знаем, что нам надоwink.gif). Конечно, если в такой функции не обеспечить атомарность, то получим тот же негативный результат. Но язык тут нипричём. Это мы не обеспечили при при написании функции её реентерабельность. Мы знаем, что прерывания могут быть, и позволяем себе такую небрежность. Но это ввод-вывод. Аналогичная по проблематике фигня с математическими библиотеками, не обеспечивающими реентерабельность - можно по незнанию/непониманию/нежеланию напороться на весь цвет проблемwink.gif))
http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%...%81%D1%82%D1%8C

Так что давайте не путать тёплое и мягкое. Если мне нужен портируемый код, то я забочусь о разделении участков кода на портируемый и от чего-то зависимый и по месту определяю - буду разруливать #ifdef-ами или вааще отдельный форк накатаю.
ЗЫ. На ассемблере не пишу без особой необходимости. Да и не особо умею. Но читаю "со словарём" без проблем. Понимать особенности приходится. Не являюсь ярым противником писания программ на ассемблере, но у нас производственная необходимость вообще не позволяет без оснований применять даже ассемблерные вставки. Это отдельная тема.


--------------------
aka Vit
Go to the top of the page
 
+Quote Post
defunct
сообщение Oct 6 2007, 12:02
Сообщение #3


кекс
******

Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326



Цитата
Недавно на телесисях была поучительная ветка. Вот она
......
Прочитайте её, интересно.

"программу на C для Tiny13" я вообще не понимаю.

Но если абстрагироваться от t13, достаточно только глянуть на код чтобы сказать - будут проблемы.
Для обеспечения портируемости полностью соглашусь с sensor_ua - надо:
1. разделять код на портируемый участок и аппаратно записимый.
2. описывать функции ввода/вывода.

Цитата
Легко портируются те программы, которые фактически не зависят от аппаратуры, например, математические вычисления (БПФ, фильтры и т.п.).

Если работа с аппаратурой - не самоцель портируемой программы, то может так случиться что при переходе с платформы на платформу вообще ничего не переписывается... пример - IP стек.

А для работы с аппаратурой есть небольшая прослойка - ОС, которая и затачивается под требуемые платформы.
Go to the top of the page
 
+Quote Post
sensor_ua
сообщение Oct 6 2007, 12:20
Сообщение #4


Профессионал
*****

Группа: Свой
Сообщений: 1 266
Регистрация: 22-04-05
Из: Киев
Пользователь №: 4 387



Цитата
А для работы с аппаратурой есть небольшая прослойка - ОС, которая и затачивается под требуемые платформы.

ИМХО, скорее BSP, который протачивается , если предполагается использование ОС, под конкретную/ые ОС.


--------------------
aka Vit
Go to the top of the page
 
+Quote Post
Petka
сообщение Oct 6 2007, 12:52
Сообщение #5


Профессионал
*****

Группа: Свой
Сообщений: 1 453
Регистрация: 23-08-05
Пользователь №: 7 886



Цитата(CD_Eater @ Oct 6 2007, 13:03) *
Очень хочется покидать камушки в огород тех, кто пишет на Си и верит в миф о лёгком портировании кода.

ИМХО выделенная часть фразы лишняя.
Проблемма не в Си а в заблуждениях.
Цитата(CD_Eater @ Oct 6 2007, 13:03) *
Это почти классический пример - ЯВУ скрыл от программиста важнейшую деталь реализации:

Скрыл? Ужас! Ну и что? Хотите открою тайну? Одна строчка в большинстве языков не "превращается" в одну машинную команду. wink.gif
Цитата(CD_Eater @ Oct 6 2007, 13:03) *
Надеяться на лёгкую портируемость проектов с одного МК на другой - это путь в страну Грабляндию.

А писать каждый раз непортируемые программы и алгоритмы это путь в ту же страну, только проездом через "Геморляндию"
Цитата(CD_Eater @ Oct 6 2007, 13:03) *
P.S. Сейчас меня обвинят в том, что я не люблю или не умею писать портируемые программы, что большинство программистов в мире пишут только портируемый код, и что за портируемым кодом будущее. В ответ замечу одну большую разницу - не сравнивайте программирование для PC и для МК.

Тот пример, который Вы привели как раз показывает то, что разницы между PC и МК при программировании на ЯВУ нет. Проблема синхронизации задач и разделения обхих ресурсов при многозадачности, успешно решаемая на PC аналогично должна решаться и на МК. Если "думать узко" то и будут возникать такие непредсказуемые и загадочные проблемы. Успешно пишу большие и сложные программы на Си под PC и МК. и ни разу не пожалел что ЯВУ скрыл от меня какие-то мифически "важные детали реализации". Гораздо приятнее отладить алгоритм на ПК, а потом практически без переделок применить его в МК. Си это позволяет. А вот с асмом придётся сходить в "грябляндию" =)
Go to the top of the page
 
+Quote Post
Прохожий
сообщение Oct 6 2007, 13:21
Сообщение #6


Cундук
*****

Группа: Участник
Сообщений: 1 478
Регистрация: 13-11-06
Из: Ростов-на-Дону
Пользователь №: 22 269



Цитата(defunct @ Oct 6 2007, 16:02) *
"программу на C для Tiny13" я вообще не понимаю.

Но если абстрагироваться от t13, достаточно только глянуть на код чтобы сказать - будут проблемы.
Для обеспечения портируемости полностью соглашусь с sensor_ua - надо:
1. разделять код на портируемый участок и аппаратно записимый.
2. описывать функции ввода/вывода.
Если работа с аппаратурой - не самоцель портируемой программы, то может так случиться что при переходе с платформы на платформу вообще ничего не переписывается... пример - IP стек.

А для работы с аппаратурой есть небольшая прослойка - ОС, которая и затачивается под требуемые платформы.

Т.е., обобщая по-порядку:
1. Подбираем подходящую ОС и с помощью напильника в виде смеси С и АСМ (или С в чистом виде?)подгоняем ее под желаемую функциональность.
2. Опять же разделяем софт на 2 части - аппаратно-зависимый и нет (тоже работа).
3. Аппаратную часть пишем на смеси С и АСМ (или С в чистом виде?).
4. Независимый кусок пишем на С или С++.
Поправьте, если что-то пропустил.
И что, такой подход однозначно гарантирует отсутствие обсуждаемых ошибок? Или уменьшает затраты на их поиск?
Go to the top of the page
 
+Quote Post
Petka
сообщение Oct 6 2007, 13:44
Сообщение #7


Профессионал
*****

Группа: Свой
Сообщений: 1 453
Регистрация: 23-08-05
Пользователь №: 7 886



Цитата(Прохожий @ Oct 6 2007, 17:21) *
Т.е., обобщая по-порядку:
............
...........
И что, такой подход однозначно гарантирует отсутствие обсуждаемых ошибок? Или уменьшает затраты на их поиск?

Совсем нет. Ошибки ошибками, а переносимость переносимостью. При переносимости переносится как работающий код, так и ошибки в нём.
Go to the top of the page
 
+Quote Post
sensor_ua
сообщение Oct 6 2007, 13:54
Сообщение #8


Профессионал
*****

Группа: Свой
Сообщений: 1 266
Регистрация: 22-04-05
Из: Киев
Пользователь №: 4 387



Цитата
Т.е., обобщая по-порядку:

Даже не смешно.
Цитата
1. Подбираем подходящую ОС и с помощью напильника в виде смеси С и АСМ (или С в чистом виде?)подгоняем ее под желаемую функциональность.

1. Учимся оценивать требуемую производительность, считаем ресурсы и затраты при использовании имеющихся заделов софта. Определяем время/трудо-затраты на применение. Кто написал код для предыдущей железки неперетачиваемый под новую железку (по вине писателя) отправляется в садwink.gif
Цитата
2. Опять же разделяем софт на 2 части - аппаратно-зависимый и нет (тоже работа).

Софт для начала разделяется на написанный, возможный для применения, и ненаписанный. Труда здесь - проверить соответствие функциональной схемы и софтария на предмет 10 отличий. Тот софт, который был уже написан, должен был быть при написании разделён на аппаратно-зависимый и портируемый. Кто сделал вначале не так - в сад. Ибо нефиг, к примеру, переписывать функцию вычисления синуса, если поменялся камень с ATmega16 на ATmega128, а если приходится, то объяснить такие растраты нормальному начальнику тяжело - может отправить в сад.
Цитата
3. Аппаратную часть пишем на смеси С и АСМ (или С в чистом виде?).

Пишем по возможности не на асме, потому как часть может быть также портируема в рамках как минимум семейства камней (например, работа с УАРТом в м8 и м88 похожа, но оно разное как минимум из-за разных имён регистров, разного количества управляющих регистров и т.п. Но работа спортами от этого не меняется) - если протачивание несущественно по сложности/затратам, то разруливается #ifdef-ами.
Цитата
4. Независимый кусок пишем на С или С++.

А есть что-то против (кроме ответственно проведенного п.1 - всмысле программист отправляет электронщика в сад из-за несоответствия ресурсов поставленной задаче)?
Цитата
И что, такой подход однозначно гарантирует отсутствие обсуждаемых ошибок?

wink.gif Чтобы ошибок не было, нужно ничего не делать.
Цитата
Или уменьшает затраты на их поиск?

Уменьшает и существенно - уже отлаженный софт трогать не надо.


--------------------
aka Vit
Go to the top of the page
 
+Quote Post
SSerge
сообщение Oct 6 2007, 13:59
Сообщение #9


Профессионал
*****

Группа: Свой
Сообщений: 1 719
Регистрация: 13-09-05
Из: Novosibirsk
Пользователь №: 8 528



И в конце концов приходим к неутешительному (для некоторых) выводу:
"Что ни делай, а головой думать всё равно придётся!"
smile.gif


--------------------
Russia est omnis divisa in partes octo.
Go to the top of the page
 
+Quote Post
Прохожий
сообщение Oct 6 2007, 14:02
Сообщение #10


Cундук
*****

Группа: Участник
Сообщений: 1 478
Регистрация: 13-11-06
Из: Ростов-на-Дону
Пользователь №: 22 269



Цитата(Petka @ Oct 6 2007, 17:44) *
Совсем нет. Ошибки ошибками, а переносимость переносимостью. При переносимости переносится как работающий код, так и ошибки в нём.

Оно-то понятно. Но имелись ввиду ошибки, связанные именно с портируемостью, типа тех, что во всяких-разных ОС закрыты критическими секциями. От этого такой подход гарантирует?
К примеру. В основном теле вычисляем некий 16 битный параметр. Его, затем, передаем в обработчик прерывания путем двух операций байтовой пересылки. Где гарантия того, что прерывание не наступит сразу после первой команды, если человек забыл его запретить перед выполнением этих двух команд?
А если человек портирует софт с 16 разрядного МК на 8 разрядный? Или с 32-х разрядного на 16-ти разрядный?
Go to the top of the page
 
+Quote Post
ArtemKAD
сообщение Oct 6 2007, 14:23
Сообщение #11


Профессионал
*****

Группа: Свой
Сообщений: 1 508
Регистрация: 26-06-06
Из: Киев
Пользователь №: 18 364



Цитата
А есть что-то против (кроме ответственно проведенного п.1 - всмысле программист отправляет электронщика в сад из-за несоответствия ресурсов поставленной задаче)?

После чего электронщик становится в позу и потратив раза в два больше времени чем это уходит у программиста пишет не переносимый код который влазит в это железо + еще процентов ...дцать остается для дальнейшего развития.

После чего следует вопрос программисту - а на кой закладывать в устройство ресурсы, которые в 2-4 раза превышают требуемые для решения задачи (причем как по объему так и по скорости)?

Сообщение отредактировал ArtemKAD - Oct 6 2007, 14:23
Go to the top of the page
 
+Quote Post
sensor_ua
сообщение Oct 6 2007, 14:45
Сообщение #12


Профессионал
*****

Группа: Свой
Сообщений: 1 266
Регистрация: 22-04-05
Из: Киев
Пользователь №: 4 387



Цитата
От этого такой подход гарантирует?

Не очень понятна суть вопроса. Если портировать, то портировать. Если писать новое, то писать. Если умеете писать, то прочитать некий код и увидеть ОЖИДАЕМЫЕ потенциальные проблемы сможете. Бездумно присоплить можно и кактус к берёзе, но елочка в пятнышко не получитсяwink.gif
Цитата
В основном теле вычисляем некий 16 битный параметр. Его, затем, передаем в обработчик прерывания путем двух операций байтовой пересылки. Где гарантия того, что прерывание не наступит сразу после первой команды, если человек забыл его запретить перед выполнением этих двух команд?

Тут Вы что-то напутали. В обработчик ничего не передаём - он сам своё возьмётwink.gif Но проблема понятна - как пример переменная отсчёта таймаута, модифицируемая (обычно декрементом) в обработчике прерывания таймера. Заботиться нужноwink.gif
Вот пример критической секции от ReAl (я, правда мог чего чуток зацепить, но работоспособность сохранена) - так пишу
#define ATOMIC_CODE(_code_) do { \
unsigned char sreg = SREG; \
cli(); \
{ _code_; } \
SREG = sreg; \
} while(0)

Но читаю uint16 переменную (так как проверяю на ноль)
с помощью такого

#define Lo(X) (*(((unsigned char*)&X)+0))
#define Hi(X) (*(((unsigned char*)&X)+1))

#define __countdown16(X) ((Hi(X))?true:(!!(Lo(X))))
получается несколько компактнее (в силу специфики задачи)

Цитата(ArtemKAD @ Oct 6 2007, 17:23) *
После чего электронщик становится в позу и потратив раза в два больше времени чем это уходит у программиста пишет не переносимый код который влазит в это железо + еще процентов ...дцать остается для дальнейшего развития.

После чего следует вопрос программисту - а на кой закладывать в устройство ресурсы, которые в 2-4 раза превышают требуемые для решения задачи (причем как по объему так и по скорости)?

Потратить в 2 раза больше времени можно за свой счёт. ЗА это время конкурент может выйти на рынок с изделием с чуть бОльшей себестоимостью, но его потом уже можно и не выдавить.
Если же задача сэкономить пару центов на кремнии и получить огромный гешефт от неслабой партии изделий, то тут программиста, не умеющего сделать глубоко оптимизированный код, нужно гнать или воспитывать. Но портируемость и глубокая привязка (считай АСМ) обычно не сидят рядом. Портируемый софт уменьшает time-to-market, но может (не всегда) требовать несколько бОльший ресурс по кремнию. Плясать нужно, ИМХО, от денег. А кремний всё дешевеет. Сегодня LPC2138 стОит в розницу почти также, а LPC2103 меньше, чем ATmega128. А LPC2378 дешевле ATmega2560. Да, самые дешёвые чипы конкурируют только между собой, но всё относительно. Если задача чуть сложнее, чем может влезть в ATtiny13, то следующее ценовое предложение сразу даёт в сравнении запас по ресурсам. Для изделий же с небольшими партиями разница по оплате труда программиста и затратам на кремний будет заметна - нынче дешевле кремнийwink.gif
А насчёт 2-4 раза, то посмотрите сами, где необходимость, а где "так получилось". Не нужно обобщать.


--------------------
aka Vit
Go to the top of the page
 
+Quote Post
Прохожий
сообщение Oct 6 2007, 14:49
Сообщение #13


Cундук
*****

Группа: Участник
Сообщений: 1 478
Регистрация: 13-11-06
Из: Ростов-на-Дону
Пользователь №: 22 269



Цитата(sensor_ua @ Oct 6 2007, 18:27) *
Не очень понятна суть вопроса. Если портировать, то портировать. Если писать новое, то писать. Если умеете писать, то прочитать некий код и увидеть ОЖИДАЕМЫЕ потенциальные проблемы сможете. Бездумно присоплить можно и кактус к берёзе, но елочка в пятнышко не получитсяwink.gif

А если цейтнот? И хочется все-таки бездумно?
Цитата(sensor_ua @ Oct 6 2007, 18:27) *
Тут Вы что-то напутали. В обработчик ничего не передаём - он сам своё возьмётwink.gif Но проблема понятна - как пример переменная отсчёта таймаута, модифицируемая (обычно декрементом) в обработчике прерывания таймера. Заботиться нужноwink.gif

Я ничего не напутал. Переменная модифицируется в основной программе, а потом передается в обработчик (логически). Вы подразумеваете сам механизм. А он может быть абсолютно разный, даже с применением флагов. Скажем так, модифицировали переменную за две команды, выставили флаг. В обработчике - опросили флаг, если он активный, забрали данные и сбросили флаг.
Go to the top of the page
 
+Quote Post
rezident
сообщение Oct 6 2007, 14:57
Сообщение #14


Гуру
******

Группа: Свой
Сообщений: 10 920
Регистрация: 5-04-05
Пользователь №: 3 882



Цитата(Прохожий @ Oct 6 2007, 20:49) *
Вы подразумеваете сам механизм. А он может быть абсолютно разный, даже с применением флагов. Скажем так, модифицировали переменную за две команды, выставили флаг. В обработчике - опросили флаг, если он активный, забрали данные и сбросили флаг.

А какие еще другие способы существуют? Я в таких случаях передачи параметров только способы с флагами использую.
Go to the top of the page
 
+Quote Post
sensor_ua
сообщение Oct 6 2007, 15:00
Сообщение #15


Профессионал
*****

Группа: Свой
Сообщений: 1 266
Регистрация: 22-04-05
Из: Киев
Пользователь №: 4 387



Цитата
А если цейтнот? И хочется все-таки бездумно?

Дык кто мешает?wink.gif Вспоминается Шри Ауробиндо - писал примерно такое: "в мире не много людей, умеющих думать, но ещё меньше людей, умеющих не думать". Результат может быть самый разный. Но "если всё заработало сразу, то количество ошибок чётное"(С).
Цитата
Я ничего не напутал.

Извините, но, похоже я тугодум. Ну не понимаю и всё. Либо давайте конкретику, а то не доходит, что же обсуждатьwink.gif


--------------------
aka Vit
Go to the top of the page
 
+Quote Post

5 страниц V   1 2 3 > » 
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 24th July 2025 - 00:39
Рейтинг@Mail.ru


Страница сгенерированна за 0.01512 секунд с 7
ELECTRONIX ©2004-2016