• XSS.stack #1 – первый литературный журнал от юзеров форума

[goto] vs [do/while/for + break]

[goto] vs [do/while/for + break]

fadey_ldr

RAID-массив
Пользователь
Регистрация
15.05.2023
Сообщения
51
Реакции
17
Для преждевременного выхода из блока (а так же что бы не плодить кучу вложенных if else) часто испольуют либо goto, либо различные варианты циклов с обязательным break в конце
1699896046557.png


1699896065041.png

С одной стороны goto избавляет от вермишели из if else, с другой стороны когда метка находится в конце блока прямо перед закрытием скобки - выглядит это не очень красиво и читаемо.
В то же время do/while/for + break создает дополнительную вложенность, так же как при использовании if else.
1699896224577.png


1699896241844.png

Хотелось услышать мнение формучан, кто каким вариантом пользуется и почему, выслушать все за и против.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Вообще, это все от того, что Цэ - неэкспрессивный язык, поэтому и приходится по всякому изгаляться, чтобы ресурсы освобождать и не просирать. В C23 был пропозал о том, чтобы добавить в стандарт языка defer, но я особо не следил, доедет/доехало это до стандарта или нет (пока можно довольствоваться __attribute__((cleanup)) в GCC/MinGW). RAII избавило бы и от первого и от второго варианта.

По сабжу: я бы использовал goto. И да: те, кто говорят, что goto - это зло потому, что какой-то уважаемый дед (Дейкстра вроде, не?) сто лет назад так сказал, не понимают, что это инструмент, а не религия, если удобно, то пользуйся... Да и вообще не понимают, чем goto плох, кроме того, что об этом сказал уважаемый дед. В Цэ - этот ваш goto вполне оправдан.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Лично я стараюсь избегать goto.
Но тут еще зависит от того, на каком языке ты пишешь. (C или C++)
Представим ситуацию 1:
C:
#define memory_allocation(size) ... // тут аллокация памяти
BOOL fn(){
    BYTE* bytes_buffer = memory_allocation(128);
    if(!bytes_buffer)
        return FALSE;
    
    HANDLE hFile = CreateFileW(...);
    if(hFile == INVALID_HANDLE_VALUE){
        memory_free(bytes_buffer);
        return FALSE;
    }
    
    
    CloseHandle(hFile);
    memory_free(bytes_buffer);
    return TRUE;
}

Как видишь, тут есть повторение кода с memory_free. И вариант с goto тут кажется вполне нормальным. То есть примерно так:
C:
BOOL fn(){
    BOOL result = FALSE;
    BYTE* bytes_buffer = memory_allocation(128);

    if(!bytes_buffer)
        return result;

    HANDLE hFile = CreateFileW(...);
    if(hFile == INVALID_HANDLE_VALUE) goto exit;
    
    // some code
    result = TRUE;   
    
exit:
    if(hFile != INVALID_HANDLE_VALUE)
        СloseHandle(hFile);
    memory_free(bytes_buffer);
    // тут может быть освобождение еще каких-то ресурсов
    return TRUE;
}

Я так никогда не писал, потому что всегда освобождал ресурсы после того, как они мне больше не нужны, а не под конец функции.
Это касалось языка C. На C++ ты просто пишешь класс обертку для HANDLE, который в деструкторе сделает вызов CloseHandle. То есть используешь идиому RAII. С указателем на данные тоже самое (смарт поинтеры юзаешь).

Ситуация 2: у тебя большое кол-во вложенных циклов. Честно говоря, я с таким никогда не сталкивался. Если не мог бы применить выход по каким-то условиям из всех циклов, то я бы юзал goto.

goto спорная вещь. У всех очень разное мнение насчёт неё. Где-то она оправдана, где-то можно просто сделать по-другому.
 
В 90-х из каждого утюга вещали, что goto - зло. Поддавшись, тоже придерживался такой точки зрения.
Но потом вышли в паблик сорцы DN - и я там увидел в коде goto! Мой мир сломался сразу :)

Поэтому goto использовать можно и иногда даже нужно.

Лично я использую while (1) - это сразу даёт понять коллегам, что тут у нас бесконечный цикл, а не какая-то непонятная метка будет в середине кода, которая потребует лишних усилий для понимания.
 
Вообще, это все от того, что Цэ - неэкспрессивный язык, поэтому и приходится по всякому изгаляться, чтобы ресурсы освобождать и не просирать. В C23 был пропозал о том, чтобы добавить в стандарт языка defer, но я особо не следил, доедет/доехало это до стандарта или нет (пока можно довольствоваться __attribute__((cleanup)) в GCC/MinGW). RAII избавило бы и от первого и от второго варианта.

По сабжу: я бы использовал goto. И да: те, кто говорят, что goto - это зло потому, что какой-то уважаемый дед (Дейкстра вроде, не?) сто лет назад так сказал, не понимают, что это инструмент, а не религия, если удобно, то пользуйся... Да и вообще не понимают, чем goto плох, кроме того, что об этом сказал уважаемый дед. В Цэ - этот ваш goto вполне оправдан.
Also here is another godfather talking about gotos: Structured programming with go to statements - D. Knuth
 
тут дело не в GOTO, а в том как мыслит кодер

когда ресурсов много, а еще они разношерстные, сначала ресурсы должны выделяться, и оформляться в контейнер, например в struct, в конце работы освобождаться, в процессе обработки по принципу билдера, из-за которого и появляются goto/do-while(0) нужно обработать содержимое структуры и если пошло что-то не по плану вернуть код ошибки, вышестоящий код видит ошибку и сам уже разрушает контейнер освобождая все ресурсы

Код:
struct SOMETHING
{
    HANDLE file_handle;
    LPVOID mem_ptr;
    SIZE_T mem_size;
};

SOMETHING* AllocSomething()
{
   SOMETHING* t = (SOMETHING*)malloc(sizeof(SOMETHING));
   if (t != nullptr)
   {
        // инициализация пустыми значениями
   }
   return t;
}

bool BuildSomething(SOMETHING* p)
{
     bool r = false;
     if (p != nullptr)
     {
         p->file_handle = CreateFile(....);
         if ( p->file_handle == INVALID_HANDLE_VALUE)
               return false;
         ...
     }

     return r;
}

SOMETHING* CreateSomething()
{
      SOMETHING* t = AllocSomething();
      if (t != nullptr)
      {
           if (BuildSomething(t) == true) return t;
           else FreeSomething(t);
      }
     return nullptr;
}

void FreeSomething(SOMETHING* p)
{
   if (p != nullptr)
   {
      // освобождение ресурсов
      free(p);
   }
}

P.S. Процедура BuildSomething делает то же самое, что и процедуры в которых используется goto, только тут можно сразу возвращать return ничег оне очищая. Процедура AllocSomething и BuildSomething внутренние, объявляется только в CPP, внешней является CreateSomething и FreeSomething.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх