Perché lo scorrimento al termine di una funzione non nulla senza la restituzione di un valore non produce un errore del compilatore?

StackOverflow https://stackoverflow.com/questions/1610030

  •  05-07-2019
  •  | 
  •  

Domanda

Da quando ho capito molti anni fa che questo non produce un errore di default (almeno in GCC), mi sono sempre chiesto perché?

Comprendo che è possibile emettere flag di compilatore per generare un avviso, ma non dovrebbe sempre essere un errore? Perché ha senso che una funzione non nulla non restituisca un valore valido?

Un esempio come richiesto nei commenti:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

... compila.

È stato utile?

Soluzione

Gli standard C99 e C ++ non richiedono che le funzioni restituiscano un valore. L'istruzione di ritorno mancante in una funzione di restituzione del valore verrà definita (per restituire 0 ) solo nella funzione main .

La logica include che verificare se ogni percorso di codice restituisce un valore è piuttosto difficile e un valore di ritorno può essere impostato con assemblatore incorporato o altri metodi complicati.

Da C ++ 11 bozza:

§ 6.6.3 / 2

  

L'uscita dalla fine di una funzione [...] provoca un comportamento indefinito in una funzione che restituisce valore.

§ 3.6.1 / 5

  

Se il controllo raggiunge la fine di main senza incontrare un'istruzione return , l'effetto è quello dell'esecuzione

return 0;

Si noti che il comportamento descritto in C ++ 6.6.3 / 2 non è lo stesso in C.


gcc ti darà un avviso se lo chiami con l'opzione -Wreturn-type.

  

Tipo di ritorno Avvisa ogni volta che una funzione viene definita con un tipo di ritorno che   il valore predefinito è int. Avvisa anche di qualsiasi   dichiarazione di ritorno senza valore di ritorno   in una funzione il cui tipo di ritorno non lo è   vuoto (cadendo dalla fine del   il corpo della funzione è considerato di ritorno   senza un valore) e su un ritorno   istruzione con un'espressione in a   funzione il cui tipo di ritorno è nullo.

     

Questo avviso è abilitato da -Wall .


Proprio come una curiosità, guarda cosa fa questo codice:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

Questo codice ha un comportamento formalmente indefinito e in pratica è convenzione di chiamata e architettura dipendente. Su un particolare sistema, con un particolare compilatore, il valore restituito è il risultato della valutazione dell'ultima espressione, memorizzato nel registro eax del processore di quel sistema.

Altri suggerimenti

gcc non verifica per impostazione predefinita che tutti i percorsi di codice restituiscano un valore perché in generale questo non può essere fatto. Si presume che tu sappia cosa stai facendo. Considera un esempio comune usando le enumerazioni:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

Il programmatore sa che, salvo un bug, questo metodo restituisce sempre un colore. gcc confida di sapere cosa stai facendo, quindi non ti costringe a mettere un ritorno in fondo alla funzione.

javac, d'altra parte, cerca di verificare che tutti i percorsi del codice restituiscano un valore e genera un errore se non può provare che tutti lo fanno. Questo errore è richiesto dalla specifica del linguaggio Java. Nota che a volte è sbagliato e devi inserire una dichiarazione di reso non necessaria.

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

È una differenza filosofica. C e C ++ sono linguaggi più permissivi e affidabili rispetto a Java o C # e quindi alcuni errori nelle nuove lingue sono avvisi in C / C ++ e alcuni avvisi vengono ignorati o disattivati ??per impostazione predefinita.

Intendi, perché non è un errore sfuggire alla fine di una funzione di restituzione del valore (cioè uscire senza un return esplicito?

In primo luogo, in C se una funzione restituisce qualcosa di significativo o no è fondamentale solo quando il codice in esecuzione utilizza il valore restituito. Forse la lingua non voleva costringerti a restituire nulla quando sai che non lo userai la maggior parte delle volte.

In secondo luogo, apparentemente le specifiche del linguaggio non volevano forzare gli autori del compilatore a rilevare e verificare tutti i possibili percorsi di controllo per la presenza di un return esplicito (sebbene in molti casi non sia così difficile fare). Inoltre, alcuni percorsi di controllo potrebbero portare a funzioni non di ritorno , il tratto generalmente sconosciuto al compilatore. Tali percorsi possono diventare una fonte di fastidiosi falsi positivi.

Si noti inoltre che C e C ++ differiscono nelle loro definizioni del comportamento in questo caso. In C ++, semplicemente, il fatto di uscire dalla fine di una funzione di ritorno valore è sempre un comportamento indefinito (indipendentemente dal fatto che il risultato della funzione sia utilizzato dal codice chiamante). In C questo provoca un comportamento indefinito solo se il codice chiamante tenta di utilizzare il valore restituito.

È legale in C / C ++ non tornare da una funzione che pretende di restituire qualcosa. Esistono numerosi casi d'uso, come la chiamata exit (-1) o una funzione che lo chiama o genera un'eccezione.

Il compilatore non rifiuterà il C ++ legale anche se porta a UB se glielo stai chiedendo. In particolare, stai chiedendo di generare nessun avviso . (Gcc attiva ancora alcuni per impostazione predefinita, ma quando aggiunti quelli sembrano allinearsi con nuove funzionalità e non nuovi avvisi per le vecchie funzionalità)

La modifica del gcc no-arg predefinito per l'emissione di alcuni avvisi potrebbe essere una rottura sostanziale per gli script esistenti o creare sistemi. Quelli ben progettati o -Wall e gestiscono gli avvisi o attivano gli avvisi individuali.

Imparare a usare una catena di strumenti C ++ è un ostacolo all'apprendimento come programmatore C ++, ma le catene di strumenti C ++ sono in genere scritte da e per esperti.

In quali circostanze non produce un errore? Se dichiara un tipo restituito e non restituisce qualcosa, mi sembra un errore.

L'unica eccezione che mi viene in mente è la funzione main () , che non richiede affatto un'istruzione return (almeno in C ++; non avere a portata di mano uno degli standard C). Se non viene restituito, funzionerà come se return 0; sia l'ultima istruzione.

Sembra che tu debba attivare gli avvisi del compilatore:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$

Credo che ciò sia dovuto al codice legacy (C non ha mai richiesto la dichiarazione di ritorno, così come C ++). Probabilmente esiste una base di codice enorme che si basa su quella "funzione". Ma almeno c'è -Werror = return-type flag su molti compilatori (inclusi gcc e clang).

È una violazione del vincolo in c99, ma non in c89. Contrasto:

C89:

  

3.6.6.4 L'istruzione return

     

Vincoli

     

Un'istruzione return con un'espressione non deve apparire in a   funzione il cui tipo di ritorno è void .

C99:

  

6.8.6.4 L'istruzione return

     

Vincoli

     

Un'istruzione return con un'espressione non deve apparire in una funzione il cui tipo di ritorno è void . Un'istruzione return senza espressione deve apparire solo in una funzione il cui tipo di ritorno è void .

Anche in modalità --std = c99 , gcc emetterà solo un avviso (anche se senza la necessità di abilitare ulteriori flag -W , come richiesto per impostazione predefinita o in C89 / 90).

Modifica per aggiungere quello in c89, " raggiungere il } che termina una funzione equivale a eseguendo un'istruzione return senza un'espressione " (3.6.6.4). Tuttavia, in c99 il comportamento non è definito (6.9.1).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top