Le regole di rottura nello standard (ANSI C) portano a o equivalgono al codice buggy?

3

Per quanto ne so, gli standard sono principalmente per la portabilità del codice tra i compilatori, ma sono curioso di sapere se alcune cose nello standard che sono considerate come avvertimenti quando non vengono seguite possono far fallire effettivamente il software, o se il codice è scritto che non è conforme allo standard e compila / esegue dove è necessario, può essere lasciato inutilizzato di solito?

Ecco un esempio del libro Deep C Secrets (le spiegazioni sul perché ciò avvenga saranno al di sotto di tutto). Non sono sicuro che questo problema specifico potrebbe causare il caos possibile (sebbene sia un cast implicito per tipi altrimenti incompatibili), ma è l'esempio che mi ha posto la domanda.

void foo (const char ** p) {}

int main (int argc, char ** argv)
{
    foo(argv);   //compiler says "line 5: warning: argument is incompatible with prototype"
    return 0;
}

Per il gusto dell'argomento, supponiamo che lo sviluppatore dica "no big deal" e decida di non correggere l'avvertenza dal momento che il codice viene compilato (sul compilatore). Se questo, ipoteticamente, potrebbe causare la rottura del software, perché non si tratta di un errore? Oppure un avviso viene semplicemente richiamato perché alcuni altri compilatori potrebbero non supportare l'assegnazione di const char ** = char ** ?

perché succede (o semplicemente controlla le FAQ C) :

A pagina 92 di questo standard, il terzo vincolo possibile sotto (1) per compiti semplici è quello:

  • entrambi gli operandi sono puntatori a tipi compatibili (con o senza qualifiche)
  • il tipo puntato dall'operando di sinistra ha tutti i qualificatori (o più) rispetto al tipo puntato dall'operando di destra

In questo caso, char ** e const char ** sono entrambi puntatori a diversi tipi non qualificati (puntatori, da uno a const char * e, dall'altro a char * ).

    
posta galois 30.07.2016 - 08:27
fonte

2 risposte

8

To my understanding, standards are mainly for code portability between compilers

No, un linguaggio di programmazione è una specifica (scritto in alcuni report in inglese con alcune formalizzazioni) e tale specifica è (spesso) lo standard. Alcune implementazioni (in particolare i GCC compilatori) accettano (e documentano) utili extensions allo standard (come statement-exprs & etichette-come-valori , ed entrambi sono accettati da < a href="http://clang.llvm.org/"> Clang troppo).

BTW ANSI C, a.k.a. C89, è uno standard obsoleto. Dovresti seguire C99 o forse C11 .

Il tuo compilatore ha ragione di avvisarti. E c'è una semplice soluzione: aggiungi un cast esplicito per la leggibilità:

foo((const char**)argv);

è probabile che il codice macchina generato sia lo stesso (su computer o laptop usuali), ma il codice migliorato è più leggibile (per te, o per lo sviluppatore che lavora sullo stesso codice).

Si noti che su alcuni (strani) computer, i dati di const potrebbero essere manipolati in modo diverso rispetto a uno non-const. Ad esempio, potrebbe trovarsi in uno spazio di indirizzi diverso o in un diverso tipo di memoria (ROM o RAM) o caricato con istruzioni diverse (ad esempio su alcuni Architetture di Harvard , dove i dati const si troverebbero nello spazio delle istruzioni).

Anche const e restrict potrebbero abilitare più ottimizzazioni dal compilatore (ad esempio, memorizzando nella cache del valore caricato in un registrarlo e riutilizzarlo di più), quindi il codice generato sarebbe (a volte) diverso, ad es all'interno di una funzione foo più complessa.

Una nozione difficile quando si programma in C e in C ++ è quella di comportamento non definito . Leggi il blog di Lattner: Cosa deve sapere di ogni programmatore C sul comportamento indefinito

Raccomando di abilitare tutti gli avvisi nel compilatore (ad esempio gcc -Wall -Wextra e forse anche -Werror ) e di migliorare il codice sorgente fino a quando non ricevi alcun avviso. Otterrai molto tempo per gli sviluppatori seguendo questa disciplina.

    
risposta data 30.07.2016 - 09:16
fonte
3

Questo avvertimento particolare è lì per una ragione seria: quel tipo di codice ti apre a brutti bug.

Se si passa un char * a una funzione che si aspetta un const char *, va bene. Significa solo che il compilatore non permetterà a quella funzione di modificare i caratteri puntati, anche se potrebbero essere veramente modificabili e il compilatore avrebbe permesso alla funzione chiamante di modificarli. Nessun danno fatto.

Passi un char ** a una funzione che si aspetta un const char **. Stai passando un puntatore a una funzione in attesa di un puntatore. La funzione di chiamata si aspetta che char * sia memorizzato tramite quel puntatore. La funzione chiamata si aspetta che const char * sia memorizzato tramite il puntatore. Pertanto, la funzione chiamata ti consente di memorizzare un const char *, che punta a caratteri che non devono essere modificati. La funzione di chiamata pensa che i puntatori memorizzati siano char *, quindi penserà che sia corretto modificare i caratteri puntati su.

Tutto sommato, questa chiamata crea una trappola che ti consente di modificare i caratteri const senza alcun cast.

static const char c = 'c';
void caller (void) {
    char d = 'd';
    char* p = &d;
    called (&p);
    *p = 'e';
}
void called (const char** q) {
    *q = &c;
}

La funzione chiamata () imposta p a & c, che è un const char *. Quindi p, che è il const char c, è impostato su "e" - comportamento indefinito, probabilmente un arresto anomalo o peggio.

    
risposta data 31.07.2016 - 14:42
fonte

Leggi altre domande sui tag