2016-06-08 33 views
13

Il seguente codice è, per quanto ho capito, comportamento non definito secondo lo standard C++ (sezione 7.1.5.1.4 [dcl.type.cv]/4 in particolare) .Esempio non banale di comportamento non definito con const_cast

#include <iostream> 

struct F; 
F* g; 

struct F { 
    F() : val(5) 
    { 
     g = this; 
    } 
    int val; 
}; 


const F f; 

int main() { 
    g->val = 8; 
    std::cout << f.val << std::endl; 
} 

Tuttavia, stampa "8" con ogni impostazione di ottimizzazione e compilatore che ho provato.

Domanda: Esiste un esempio che mostrerà risultati imprevisti con questo tipo di "implicit const_cast"?

spero in qualcosa di spettacolare come i risultati di

#include <iostream> 
int main() { 
    for (int i = 0; i <=4; ++i) 
     std::cout << i * 1000000000 << std::endl; 
} 

su, ad esempio, gcc 4.8.5 con -O2

EDIT: la relativa sezione dallo standard

7.1.5.1.4: Tranne che qualsiasi membro della classe dichiarato mutabile (7.1.1) può essere modificato, qualsiasi tentativo di modificare un oggetto const durante la sua durata (3 .8) comporta un comportamento indefinito.

In risposta al commento suggerendo un duplicato; non è un duplicato perché sto chiedendo un esempio in cui si verificano risultati "imprevisti".

+2

Questa è una configurazione molto subdola - molto bella. –

+7

Mi piacciono i sintomi di quello snippet in fondo. Zero, uno, due, rilascia il Kraken! – Quentin

+3

Qual è il valore nel tentativo di definire un comportamento non definito? – lcs

risposta

7

Non era spettacolare:

f.h (guardie omesso):

struct F; 
extern F* g; 

struct F { 
    F() : val(5) 
    { 
     g = this; 
    } 
    int val; 
}; 

extern const F f; 
void h(); 

TU1:

#include "f.h" 
// definitions 
F* g; 
const F f; 
void h() {}  

TU2:

#include "f.h" 
#include <iostream> 
int main() { 
    h(); // ensure that globals have been initialized 
    int val = f.val; 
    g->val = 8; 
    std::cout << (f.val == val) << '\n'; 
} 

Stampe 1 quando compilato con g++ -O2 e 0 quando compilato con -O0.

0

Il caso principale di comportamento "non definito" sarebbe che, in genere, se qualcuno vede const, assumerà che non cambia. Quindi, const_cast fa intenzionalmente qualcosa che molte librerie e programmi non si aspettano di fare o considerano come comportamento esplicito indefinito. È importante ricordare che non tutto il comportamento non definito deriva dallo standard, anche se questo è l'utilizzo tipico del termine.

Detto questo, sono stato in grado di individuare un posto nella libreria standard in cui tale pensiero può essere applicata a fare qualcosa in cui credo sarebbe più restrittivo essere considerato un comportamento indefinito: generando un std::map con le "chiavi duplicate":

#include "iostream" 
#include "map" 

int main() 
{ 
    std::map< int, int > aMap; 

    aMap[ 10 ] = 1; 
    aMap[ 20 ] = 2; 

    *const_cast< int* >(&aMap.find(10)->first) = 20; 

    std::cout << "Iteration:" << std::endl; 
    for(std::map< int,int >::iterator i = aMap.begin(); i != aMap.end(); ++i) 
     std::cout << i->first << " : " << i->second << std::endl; 

    std::cout << std::endl << "Subscript Access:" << std::endl; 
    std::cout << "aMap[ 10 ]" << " : " << aMap[ 10 ] << std::endl; 
    std::cout << "aMap[ 20 ]" << " : " << aMap[ 20 ] << std::endl; 

    std::cout << std::endl << "Iteration:" << std::endl; 
    for(std::map< int,int >::iterator i = aMap.begin(); i != aMap.end(); ++i) 
     std::cout << i->first << " : " << i->second << std::endl; 
} 

L'output è:

Iteration: 
20 : 1 
20 : 2 

Subscript Access: 
aMap[ 10 ] : 0 
aMap[ 20 ] : 1 

Iteration: 
10 : 0 
20 : 1 
20 : 2 

costruito con g++.exe (Rev5, Built by MSYS2 project) 5.3.0.

Ovviamente, vi è una mancata corrispondenza tra le chiavi di accesso e i valori chiave nelle coppie memorizzate. Sembra anche che la coppia 20: 2 non sia accessibile eccetto tramite iterazione.

La mia ipotesi è che ciò stia accadendo perché map è implementato come un albero. La modifica del valore lascia il punto in cui era inizialmente (dove 10 andava), quindi non sovrascrive l'altra chiave 20. Allo stesso tempo, l'aggiunta di un 10 attuale non sovrascrive il vecchio 10 causa sul controllo del valore della chiave, in realtà non è la stessa

Non ho uno standard di guardare in questo momento, ma mi aspetterei questo viola la definizione di map su alcuni livelli.

Potrebbe anche portare a un comportamento peggiore, ma con il mio compilatore/combo del sistema operativo non sono riuscito a farlo fare qualcosa di più estremo, come crash.

+0

@ T.C. Non posso davvero dare citazioni per un comportamento indefinito, * ma * ho sbagliato su questo punto. Ho approfondito l'analisi e l'ho aggiunta come modifica. La situazione reale è più strana. –

+0

Cosa è * soft * comportamento non definito? – nwp

+0

Se si scherza con le parti interne di una classe in un modo che viola il suo contratto e sconvolge i suoi invarianti, ovviamente andrà in pezzi, indipendentemente dal fatto che nel processo sia usato 'const_cast'. –