Почему «не все пути управления возвращают значение» является предупреждением, а не ошибкой? - PullRequest
28 голосов
/ 14 ноября 2009

Я пытался ответить на этот вопрос. Как следует из принятого ответа, проблема с этим кодом заключается в том, что не все пути управления возвращают значение. Я попробовал этот код на компиляторе VC9, и он дал мне предупреждение о том же. Мой вопрос: почему это просто предупреждение, а не ошибка? Кроме того, в случае выполнения пути, который не возвращает значение, что будет возвращено функцией (она должна что-то возвращать)? Это просто то, что находится на вершине стека, или опять страшное неопределенное поведение?

Ответы [ 7 ]

30 голосов
/ 14 ноября 2009

Если не вернуть значение из функции, которая имеет тип возврата не void, это приводит к неопределенному поведению, но не является семантической ошибкой.

Причина этого, насколько я могу судить, во многом историческая.

C изначально не имел void, а неявное int означало, что большинство функций возвращало int, если явно не объявлено, что оно возвращает что-то еще, даже если не было намерения использовать возвращаемое значение.

Это означает, что многие функции возвращали int, но без явной установки возвращаемого значения, но это было нормально, поскольку вызывающие никогда не будут использовать возвращаемое значение для этих функций.

Некоторые функции возвращали значение, но использовали неявный int, поскольку int был подходящим типом возврата.

Это означает, что в пре-1017 * коде было много функций, которые номинально возвращали int, но которые могли быть объявлены для возврата void, и множество других функций, которые должны возвращать int без четкого способа сказать различия. Применение на всех этапах кода return всех функций, отличных от void, на любом этапе нарушило бы устаревший код.

Существует также аргумент, что некоторые пути кода в функции могут быть недоступны, но это может быть нелегко определить из простого статического анализа, так зачем применять ненужные return?

9 голосов
/ 14 ноября 2009

Я думаю, это всего лишь предупреждение, потому что компилятор не всегда может быть на 100% уверен, что можно не получить возврат.

т.е. если у вас было:


-= source1.c =-
int func()
{
    if(doSomething())
    {
       return 0;
    }
}

-= source2.c =-
int doSomething()
{
    return 1;
}

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

Что касается того, что на самом деле будет возвращено, зависит от платформы. В x86 ABI EAX используется для возвращаемого значения (до 32 бит), поэтому он будет возвращать то, что когда-либо было помещено в этот регистр (что может быть возвращением чего-то другого, временного значения или общего мусора).

6 голосов
/ 13 сентября 2012

Технически это не гарантируется как ошибка, если вы вызываете функцию, и эта функция всегда выдает исключение. Например, здесь есть некоторый псевдокод, и вы знаете, что throwError всегда выдает.

MyClass func( params )
{
     if( allIsValid() )
     {
       return myObject;
     }
     else
     {
        raiseError( errorInfo );
     }
}

Если компилятор не может увидеть реализацию yieldError, он не будет знать, что функция собирается выбросить. Так что на самом деле здесь нет неопределенного поведения. Конечно, здесь хорошо заставить компилятор замолчать, что можно сделать либо написав «фиктивный» оператор return после RaiseError, либо фиктивный «throw». Я называю их «пустышками», потому что они никогда не будут достигнуты в реальности. (Вы также можете отключить предупреждение, если вы действительно настаиваете). Однако нет ошибок или неопределенного поведения.

1 голос
/ 07 августа 2015

Еще один пример, когда для некоторых путей управления может быть нормально не возвращать значение:

enum E : int {A, B};

int foo(E e) {
  switch (e) {
    case A: return 30;
    case B: return 50;
  }
}

Возможно, что e не будет A или B, но подразумевается, что оно всегда будет одним из этих значений. Если это так, то код в порядке и проблем нет. Превращение этого предупреждения в обязательную ошибку потребует ненужного, «недостижимого» беспорядка.

Если вы все равно хотите, чтобы предупреждение было ошибкой, вы можете настроить ваш компилятор так, чтобы он делал это с флагом, например / WX или -Werror. Хотя, конечно, вы должны заметить, что разные компиляторы могут по-разному определять, что недоступно, поэтому вы можете исправлять разные ошибки для разных компиляторов.

1 голос
/ 16 августа 2012

вот еще одна причина, по которой это не ошибка

следующее выдаст вам то же предупреждение, так как компилятор ожидает, что вы вернете что-то из блока catch, даже если вы бросаете туда

int foo(){
   try{
      return bar(0);
   } catch(std::exception& ex){
      //do cleanup
      throw ex;
   }
}

int bar(unsigned int i){
   if(i == 0){
      throw std::string("Value must be greater than 0");
   } else{
      return 0;
   }
}
0 голосов
/ 11 октября 2012

Рассмотрим следующий сценарий:

UINT GenderID(GENDER gender)
{
    switch(gender)
    {
    case MALE:
        return MALE_ID;
    case FEMALE:
        return FEMALE_ID;
    }
// default not required because [GENDER] in our 'Matrix' CAN be either M or F
}

компилятор C ++ должен позволить вам иметь свою «Матрицу» по-вашему; Таким образом, это не ошибка.

0 голосов
/ 14 ноября 2009

Это не ошибка, потому что это может быть предполагаемое поведение. Например, некоторые библиотеки шифрования используют неинициализированные локальные данные в качестве шага для заполнения. Поскольку возвращаемые значения хранятся в соглашениях о вызовах и в определенных местах платформы, это может помочь в некоторых необычных (например, описанных выше) ситуациях. В этом случае функция возвращает то, что осталось в регистре, используемом для возврата возвращаемого значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...