Все можно написать через функции, причем, будет красиво. Даже это: Но вызов функции тянет за собой закидывание параметров на стек, как минимум, точки выхода. Вызов следующей функции из функции - еще пожирание стека... А при использовании goto нет вызова новой функции, и по этому нет пожирания стека как при вызове новой функции
А при чём тут стек? При транслировании кода в asm компилятор заменит goto на JMP (как вариант RJMP), ибо это и есть безусловный переход. При выполнении этго в program counter запишется адрес по которому нужно перепрыгнуть и всё, через 2 тика тактового генератора выполнение программы прололжиться с нужного нам адреса. Стек при этом не задействован и флаги не меняются.
Смотрим тут: Разбираемся. Думаем как написать без goto. Потом читаем это: И осознаем это: Это про стек.
Тоже кстати об этом подумал-не главная ли это причина игнора Goto?Но в общем и целом согласен Goto-инструмент!Хороший инструмент которым нужно уметь пользоваться.Как и delay незаслуженно переданный анафеме.Я например всегда им пользуюсь особенно когда надо чтоб устройство тупо заткнулось на какое то время и не жило своей жизнью.
Посмотрел, интересно и познавательно, по про стек ни слова. Согласен на сто и даже более процентов. Смутила фраза которую вы уже подправили в своём посте (модератор как-ни-как). Но цитата в моём посте осталась в первозданном виде. Поэтому привожу её так Смоё понимание как происходит трансляция Goto в ASM я изложил. В моём понимании стек не задействован. Если я чего-то недопонимаю - готов услышать конструктивную критику.
Ну а как с inline функциями? разве не то? А конкретный рабочий код не на этой машине (другую надо доставать)... хотя вот(может и не удачный пример) Код (C++): void mClientProcess(void * ind) { //uint8_t rcv[_szbuf]; //uint8_t snd[_szbuf]; uint8_t* rcv = (uint8_t*)malloc(_szbuf); uint8_t* snd = (uint8_t*)malloc(_szbuf); S7DATA* ls7data = &s7data; CWORK* lcwork = &cwork; F1DATA* lf1data = &f1data; SETPARAM_COOL* lsetparam_cool = &setparam_cool; SETPARAM_ALARM* lsetparam_alarm = &setparam_alarm; //time_t rawtime; //struct tm *u; //u_int8_t res8; int res; int csock; int idclient; //uint16_t workU16; uint32_t sz; int cntlive = _stime_life; //счётчик времени жизни сокета // pthread_mutex_lock(&mServerMutex); MSERV * clserver = ind; csock = clserver->csock; idclient = clserver->idclient; pthread_mutex_unlock(&mServerMutex); printf("===loop client process(sock:%i)=== \n",csock); while(1) { if(!clserver->work) goto mexit; sz = 0; memset(snd, 0, _szbuf); memset(rcv, 0, _szbuf); pthread_mutex_lock(&mServerMutex); (*(uint64_t*)(snd + _PKT_TIME)) = (uint64_t)time(NULL); (*(uint64_t*)(snd +_PKT_CLCK)) = (uint64_t)clock(); pthread_mutex_unlock(&mServerMutex); res = sReadSocket(csock, &rcv[0]); #if(_ssdeb == 1) printf("SIZE:%lli PKT_TIME:%lli PKT_CLCK:%lli PKT_CMD:%02X PKT_FTYPE:%02X\n", (*(uint64_t*)(rcv + _PKT_SIZE)), (*(uint64_t*)(rcv + _PKT_TIME)), (*(uint64_t*)(rcv + _PKT_CLCK)), (*(uint8_t*)(rcv + _PKT_CMD)), (*(uint8_t*)(rcv + _PKT_FTYPE]))); #endif switch(res) { case 0: goto mexit; case -1: //таймаут #if(_ssdeb == 1) printf("TCP recv.: timeout..\r\n"); #endif //cntlive /= 2; break;//goto mexit; case -2: //какая-то ошибка сокета #if(_ssdeb == 1) printf("TCP resv.: err?..\r\n"); #endif goto mexit; case -3: //ошибка размера пакета //передача сообщения о несоответствии #if(_ssdeb == 1) printf("TCP client: frame no correct..\r\n"); #endif (*(uint32_t*)(snd + _PKT_SIZE)) = _PKT_DATA; (*(uint8_t*)(snd + _PKT_CMD)) = (*(uint8_t*)(rcv + _PKT_CMD)); (*(uint8_t*)(snd + _PKT_FTYPE)) = (*(uint8_t*)(rcv + _PKT_FTYPE)); (*(uint8_t*)(snd + _PKT_ANS)) = _ANS_SZ_NO_CORRECT; break; default: //нормально - данные приняты // #if(_ssdeb == 1) printf("WORK CMD:%02X\r\n", (*(uint8_t*)(rcv + _PKT_CMD))); #endif switch((*(uint8_t*)(rcv + _PKT_CMD))) { case _CMD_RD: //чтение блока параметров #if(_ssdeb == 1) printf("WORK FTYPE:%02X\r\n", (*(uint8_t*)(rcv + _PKT_FTYPE))); #endif switch((*(uint8_t*)(rcv + _PKT_FTYPE))) { case _DTYPE_S7: // pthread_mutex_lock(&mServerMutex); memcpy((uint8_t*)(snd + _PKT_DATA), ls7data, sizeof(S7DATA)); pthread_mutex_unlock(&mServerMutex); (*(uint32_t*)(snd + _PKT_SIZE)) = _PKT_DATA + sizeof(S7DATA); (*(uint8_t*)(snd + _PKT_CMD)) = (*(uint8_t*)(rcv + _PKT_CMD)); (*(uint8_t*)(snd + _PKT_FTYPE)) = (*(uint8_t*)(rcv + _PKT_FTYPE)); (*(uint8_t*)(snd + _PKT_ANS)) = _ANS_OK; break; //сокращено - огрничение по размеру (на сайте) ... ... default: //ошибка типа (*(uint32_t*)(snd + _PKT_SIZE)) = _PKT_DATA; (*(uint8_t*)(snd + _PKT_CMD)) = (*(uint8_t*)(rcv + _PKT_CMD)); (*(uint8_t*)(snd + _PKT_FTYPE)) = (*(uint8_t*)(rcv + _PKT_FTYPE)); (*(uint8_t*)(snd + _PKT_ANS)) = _ANS_NO_TYPE; break; } break; case _CMD_RD_ONE: //чтение одного параметра #if(_ssdeb == 1) printf("WORK FTYPE:%02X\r\n", (*(uint8_t*)(rcv + _PKT_FTYPE))); #endif switch((*(uint8_t*)(rcv + _PKT_FTYPE))) { //сокращено - огрничение по размеру (на сайте) ... ... } break; case _CMD_WR_ONE: //запись одного параметра #if(_ssdeb == 1) printf("WORK FTYPE:%02X\r\n", (*(uint8_t*)(rcv + _PKT_FTYPE))); #endif switch((*(uint8_t*)(rcv + _PKT_FTYPE))) { //сокращено - огрничение по размеру (на сайте) ... ... default: //ошибка типа (*(uint32_t*)(snd + _PKT_SIZE)) = _PKT_DATA; (*(uint8_t*)(snd + _PKT_CMD)) = (*(uint8_t*)(rcv + _PKT_CMD)); (*(uint8_t*)(snd + _PKT_FTYPE)) = (*(uint8_t*)(rcv + _PKT_FTYPE)); (*(uint8_t*)(snd + _PKT_ANS)) = _ANS_NO_TYPE; break; } break; case _CMD_DISCONNECT: //ответ не отсылается goto mexit; default: //команда не определена (*(uint32_t*)(snd + _PKT_SIZE)) = _PKT_DATA; (*(uint8_t*)(snd + _PKT_CMD)) = (*(uint8_t*)(rcv + _PKT_CMD)); (*(uint8_t*)(snd + _PKT_FTYPE)) = (*(uint8_t*)(rcv + _PKT_FTYPE)); (*(uint8_t*)(snd + _PKT_ANS)) = _ANS_CMD_UNCNOWN; break; } sz = (*(uint32_t*)(snd + _PKT_SIZE)); break; } //отправка ответа клиенту if(sz > 0) { res = sWriteSocket(csock, &snd[0], sz); switch(res) { case 0: goto mexit; case -1: //таймаут #if(_ssdeb == 1) printf("TCP -send timeout\r\n"); #endif goto mexit;//goto mt1; case -2: //ошибка передачи #if(_ssdeb == 1) printf("TCP send error\r\n"); #endif goto mexit; default: if(res != sz) goto mexit; cntlive = _stime_life; break; } } // #if(_ssdeb == 1) printf("TCP cntlive:%i\r\n", cntlive); #endif if(cntlive > _mspr_ssleep) cntlive -= _mspr_ssleep; else goto mexit; #if(_ssdeb == 1) printf("--countlive:%i---\r\n",cntlive); #endif memset(&rcv[0], 0, _szbuf); usleep(_mspr_ssleep); } mexit: shutdown(csock, SHUT_RDWR); close(csock); pthread_mutex_lock(&mServerMutex); if(clserver->work) //если всё остальное работает { clserver->midcl[idclient] = 0; clserver->cntclient--; #if(_ssdeb == 1) printf("---close client only---\r\n"); #endif } pthread_mutex_unlock(&mServerMutex); #if(_ssdeb == 1) printf("---close client and exit---\r\n"); #endif free(rcv); free(snd); pthread_exit(0); } Но уверяю что для контроллеров (не этот пример) помогает. Внутри switch ещё несколько вложенных switch... и всё это в цикле.
Если честно сказать я использую всё. Кстати попался компилятор(может я чего-то и не знаю) который криво работает с __inline. А мне это не очень по душе, потому как программа-сервер для каждого подключаемого клиента создаёт отделяемый поток (это почти что процесс) который при кривой работе станет зомби. И простые функции (самописные) должны вызываться в каждом потоке... а я не являюсь крутым перцем который может всё. А пример (он и не пример вовсе) и не тянет на оптимальность если только не ускорять и не экономить
Да и в Си исполняемый файл (бинарник) так же содержит эти самые JMP-ы в виде кода инструкции процессора. Всё логично! Хотя бы циклы эти же самые JMP-ы. И все остальные.
Как так? Стек задействован всегда. Нет? Кмк тут вопрос вот в чем— насколько в каждом конкретном случае (с goto или без онного).
В языках высокого уровня вся подноготная скрыта, а в ASM всё видно сразу. Не скажу, что я сильно в ASM разбираюсь, изложу своё видение процесса. goto в ASM это JMP и его разновидности. При его исполнении в program counter подставляется адрес перехода и программа прыгает на этот адрес. Адрес перехода хранится в програмной FLASH памяти, возврат после этого прыжка не планируется, так-что ложить в стек просто нечего. Вызов функции. В ASM это CALL и разнодидности - вызов подпрограммы(ПП). Тут у нас предусматривается возврат после исполнения ПП. По сему адрес с которого осуществляется вызов ПП процессор ложит в стек. Выполнение ПП заканчивается командой RET, которая забирает из стека адрес возврата и пихает его в program counter. Далее прыжок в точку вызова ПП. Значения регистров общего назначения (РОН) используемых в ПП обычно в стек не сохнаняют. Мы ведь ПП вызываем осознанно и к этому зарание готовимся. Прерывание. При возникновении события заканчивается выполнение текущей команды, адрес следующей ложится в стек и нас забрасывает в таблицу векторов прерываний а оттуда командой JMP в обработчик прерывания. Так как прерывание возникает внезапно, все РОН используеммые в обработчике надо тоже положить в стек. В конце обработчика мы извлекаем из стека начальные значения РОН и пишем команду RETI, которая работает так-же как и RET, только дополнительно востанавливает флаг I (глобальное резрешение прерываний). И не дай бог выходить из ПП или прерывания по JMP. Стек залезет на оперативку и всё рухнет. В моём понимании как-то так. Если вдруг по другому - готов выслушать.
Поправил сразу, чтобы понятней было. А если так: Код (C++): int func_A(int a, int b) { if (a< 10) { int c = 0; c = b-a; if (c >20) goto label_1; } return (a); label_1: return (b); } Переменная "с" - локальная, она в стеке. По goto мы выходим из блока где была выделена на стеке переменная "с". Мы вышли из блока, стек нужно вернуть до значения при котором был вход в блок? Руки не дошли проверить и приложить листинг. но так должно быть по определению. Может кто и проверит?
Именно в этом примере и для AVR, она может быть регистровая. Но если параметров и локальных переменных много, и процессор, например 8080, где регистров не так много, она будет на стеке. И никак по другому!
Не скажу про то так всё устроено в 8080, не знаю не юзал. А в AVR дела обстоят так. Хранить переменные в РОН это моветон, особенно когда этих переменных много. Переменные хранят в оперативке. А в РОН их вытаскивают только для выполнения действий. С переменными находящимися в оперативке никаких действий кроме чтения и записи выполнить нельзя. А так как стек расположен на дне оперативки, то вышесказанное относится и к нему. Так, что действия Код (C++): ........... int c = 0; c = b-a; ............. могут быть выполнены только в регистрах общего назначения. Где потом компилятор придумает хранить результат, сказать не могу, но очень сильно сомневаюсь, что в стеке. Да, стек иногда используют для хранения данных, но он устроен по принципу массива последовательного доступа. Как стопка книг. Работает так - что последним ПОЛОЖИЛ, то первым ЗАБРАЛ. Именно ПОЛОЖИЛ и ЗАБРАЛ. После однократного считывания данные становятся недоступными. Приходится постоянно следить за содержимым. А зачем этот геморой если есть куча оперативки с произвольным доступом. И читать данные оттуда можно неограниченное число раз.
Вот если не знаете, так и не пишите. Все локальные переменные хранятся в регистрах, а если их НЕ хватает- в стеке.