Domanda

Ho pensato a lungo che in C tutte le variabili dovevano essere dichiarate all'inizio della funzione. So che in C99, le regole sono le stesse di C ++, ma quali sono le regole di posizionamento delle dichiarazioni variabili per C89 / ANSI C?

Il codice seguente viene compilato correttamente con gcc -std = c89 e gcc -ansi :

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

Le dichiarazioni di c e s non dovrebbero causare un errore in modalità C89 / ANSI?

È stato utile?

Soluzione

Si compila correttamente perché GCC lo consente come estensione GNU, anche se non fa parte dello standard C89 o ANSI. Se si desidera aderire rigorosamente a tali standard, è necessario passare il flag -pedantic .

Altri suggerimenti

Per C89, è necessario dichiarare tutte le variabili all'inizio di un blocco ambito .

Quindi, la tua dichiarazione char c è valida in quanto si trova nella parte superiore del blocco dell'ambito del ciclo for. Ma la dichiarazione char * s dovrebbe essere un errore.

Raggruppare le dichiarazioni delle variabili nella parte superiore del blocco è un'eredità probabilmente a causa delle limitazioni dei vecchi compilatori C primitivi. Tutte le lingue moderne raccomandano e talvolta impongono persino la dichiarazione delle variabili locali all'ultimo punto: dove vengono inizializzate per la prima volta. Perché questo elimina il rischio di utilizzare un valore casuale per errore. Separare la dichiarazione e l'inizializzazione ti impedisce anche di utilizzare " const " (o "quot" finale) quando puoi.

C ++ purtroppo continua ad accettare il vecchio modo di dichiarare in alto per la retrocompatibilità con C (una compatibilità C trascina fuori da molte altre ...) Ma C ++ cerca di allontanarsi da essa:

  • La progettazione di riferimenti C ++ non consente nemmeno tale cima del raggruppamento di blocchi.
  • Se separi la dichiarazione e l'inizializzazione di un oggetto locale C ++, pagherai il costo di un costruttore extra per niente. Se il costruttore no-arg non esiste, non puoi nemmeno separare entrambi!

C99 inizia a spostare C nella stessa direzione.

Se sei preoccupato di non trovare dove vengono dichiarate le variabili locali, significa che hai un problema molto più grande: il blocco che la racchiude è troppo lungo e dovrebbe essere diviso.

https : //www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

Dal punto di vista della manutenibilità, piuttosto che sintattico, ci sono almeno tre treni di pensiero:

  1. Dichiara tutte le variabili all'inizio della funzione in modo che si trovino in un posto e tu possa vedere la lista completa a colpo d'occhio.

  2. Dichiara tutte le variabili il più vicino possibile al luogo in cui sono state utilizzate per la prima volta, quindi saprai perché ognuna è necessaria.

  3. Dichiara tutte le variabili all'inizio del blocco dell'ambito più interno, quindi usciranno dal campo di applicazione il prima possibile e consentiranno al compilatore di ottimizzare la memoria e di dirti se le hai usate accidentalmente dove non avevi destinato.

In genere preferisco la prima opzione, poiché trovo che gli altri spesso mi costringano a cercare il codice per le dichiarazioni. La definizione anticipata di tutte le variabili semplifica inoltre l'inizializzazione e la visualizzazione da un debugger.

A volte dichiarerò variabili all'interno di un blocco dell'ambito più piccolo, ma solo per una buona ragione, di cui ne ho pochissime. Un esempio potrebbe essere dopo un fork () , per dichiarare le variabili necessarie solo per il processo figlio. Per me, questo indicatore visivo è un utile promemoria del loro scopo.

Come notato da altri, GCC è permissivo a questo proposito (e forse altri compilatori, a seconda degli argomenti con cui sono chiamati) anche quando si è in modalità 'C89', a meno che non si usi il controllo 'pedante'. Ad essere onesti, non ci sono molti buoni motivi per non essere pedanti; il codice moderno di qualità dovrebbe sempre essere compilato senza avvisi (o pochissimi in cui sai che stai facendo qualcosa di specifico che è sospetto per il compilatore come un possibile errore), quindi se non riesci a compilare il tuo codice con una configurazione pedante probabilmente avrai bisogno di qualche attenzione.

C89 richiede che le variabili vengano dichiarate prima di qualsiasi altra affermazione all'interno di ciascun ambito, le norme successive consentono una dichiarazione più vicina all'uso (che può essere sia più intuitiva che più efficiente), in particolare la dichiarazione simultanea e l'inizializzazione di una variabile di controllo del ciclo in 'per "loop.

Come è stato notato, ci sono due scuole di pensiero su questo.

1) Dichiara tutto in cima alle funzioni perché l'anno è il 1987.

2) Dichiara il più vicino al primo utilizzo e nel più piccolo ambito possibile.

La mia risposta a questa è DO ENTRAMBI! Lasciami spiegare:

Per le funzioni lunghe, 1) rende molto difficile il refactoring. Se lavori in una base di codice in cui gli sviluppatori sono contrari all'idea delle subroutine, allora avrai 50 dichiarazioni variabili all'inizio della funzione e alcune potrebbero essere solo " i " per un for-loop che si trova in fondo alla funzione.

Da questo ho quindi sviluppato la dichiarazione al vertice del PTSD e ho provato a fare l'opzione 2) religiosamente.

Sono tornato all'opzione uno per una cosa: funzioni brevi. Se le tue funzioni sono abbastanza brevi, allora avrai poche variabili locali e poiché la funzione è breve, se le metti in cima alla funzione, saranno comunque vicine al primo utilizzo.

Inoltre, l'anti-pattern di " dichiarare e impostato su NULL " quando vuoi dichiarare in alto ma non hai fatto alcuni calcoli necessari per l'inizializzazione viene risolto perché le cose che devi inizializzare saranno probabilmente ricevute come argomenti.

Quindi ora penso che dovresti dichiarare in cima alle funzioni e il più vicino possibile al primo utilizzo. Quindi ENTRAMBI! E il modo per farlo è con subroutine ben divise.

Ma se stai lavorando su una funzione lunga, metti le cose più vicine al primo utilizzo perché in questo modo sarà più semplice estrarre i metodi.

La mia ricetta è questa. Per tutte le variabili locali, prendi la variabile e sposta la sua dichiarazione verso il basso, compila, quindi sposta la dichiarazione appena prima dell'errore di compilazione. Questo è il primo utilizzo. Fallo per tutte le variabili locali.

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Ora, definisci un blocco di ambito che inizia prima della dichiarazione e sposta la fine fino alla compilazione del programma

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

Questo non viene compilato perché c'è dell'altro codice che usa foo. Possiamo notare che il compilatore è stato in grado di esaminare il codice che utilizza bar perché non utilizza foo. A questo punto, ci sono due scelte. Quello meccanico è semplicemente spostare "} " verso il basso fino a quando non viene compilato e l'altra scelta è ispezionare il codice e determinare se l'ordine può essere modificato in:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

Se l'ordine può essere cambiato, è probabilmente quello che vuoi perché accorcia la durata dei valori temporanei.

Un'altra cosa da notare, il valore di foo deve essere preservato tra i blocchi di codice che lo usano, o potrebbe essere solo un foo diverso in entrambi. Ad esempio

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

Queste situazioni richiedono più della mia procedura. Lo sviluppatore dovrà analizzare il codice per determinare cosa fare.

Ma il primo passo è trovare il primo utilizzo. Puoi farlo visivamente, ma a volte è più semplice eliminare la dichiarazione, provare a compilare e rimetterla al di sopra del primo utilizzo. Se il primo utilizzo si trova all'interno di un'istruzione if, inseriscilo e verifica se viene compilato. Il compilatore identificherà quindi altri usi. Prova a creare un blocco ambito che comprenda entrambi gli usi.

Al termine di questa parte meccanica, diventa più semplice analizzare la posizione dei dati. Se una variabile viene utilizzata in un blocco di ambito di grandi dimensioni, analizza la situazione e vedi se stai usando la stessa variabile per due cose diverse (come un "i" che viene utilizzato per due per i loop). Se gli usi non sono correlati, creare nuove variabili per ciascuno di questi usi non correlati.

Citerò alcune dichiarazioni del manuale per la versione 4.7.0 di gcc per una chiara spiegazione.

" Il compilatore può accettare diversi standard di base, come "c90" o "c ++ 98", e dialetti GNU di tali standard, come "gnu90" o "gnu ++ 98". Specificando uno standard di base, il compilatore accetterà tutti i programmi che seguono quello standard e quelli che usano estensioni GNU che non lo contraddicono. Ad esempio, "-std = c90" disattiva alcune funzionalità di GCC che sono incompatibili con ISO C90, come le parole chiave asm e typeof, ma non altre estensioni GNU che non hanno un significato in ISO C90, come omettere la metà termine di un?: espressione. "

Penso che il punto chiave della tua domanda sia che perché non gcc sia conforme a C89 anche se l'opzione " -std = c89 " viene usato. Non conosco la versione del tuo gcc, ma penso che non ci saranno grandi differenze. Lo sviluppatore di gcc ci ha detto che l'opzione " -std = c89 " significa solo che le estensioni che contraddicono C89 sono disattivate. Quindi, non ha nulla a che fare con alcune estensioni che non hanno significato in C89. E l'estensione che non limita il posizionamento della dichiarazione variabile appartiene alle estensioni che non contraddicono C89.

Ad essere onesti, tutti penseranno che dovrebbe essere totalmente conforme a C89 a prima vista dell'opzione "-std = c89". Ma non lo fa. Per quanto riguarda il problema che dichiara che tutte le variabili all'inizio sono migliori o peggiori è solo una questione di abitudine.

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