Ricerca…


Osservazioni

Se il comportamento di un costrutto non è specificato, lo standard pone alcuni vincoli sul comportamento, ma lascia una certa libertà all'implementazione, che non è necessaria per documentare ciò che accade in una determinata situazione. Contrasta con il comportamento definito dall'implementazione , in cui è richiesta l'implementazione per documentare ciò che accade e un comportamento indefinito, in cui qualsiasi cosa può accadere.

Ordine di inizializzazione di globals su TU

Mentre all'interno di un'unità di traduzione viene specificato l'ordine di inizializzazione delle variabili globali, l'ordine di inizializzazione tra le unità di traduzione non è specificato.

Quindi programma con i seguenti file

  • foo.cpp

    #include <iostream>
    
    int dummyFoo = ((std::cout << "foo"), 0);
    
  • bar.cpp

    #include <iostream>
    
    int dummyBar = ((std::cout << "bar"), 0);
    
  • main.cpp

    int main() {}
    

potrebbe produrre come output:

foobar

o

barfoo

Ciò potrebbe portare all'ordine di inizializzazione statica Fiasco .

Valore di un enum fuori limite

Se un enum di ambito viene convertito in un tipo integrale troppo piccolo per contenere il suo valore, il valore risultante non è specificato. Esempio:

enum class E {
    X = 1,
    Y = 1000,
};
// assume 1000 does not fit into a char
char c1 = static_cast<char>(E::X); // c1 is 1
char c2 = static_cast<char>(E::Y); // c2 has an unspecified value

Inoltre, se un numero intero viene convertito in un enum e il valore dell'intero è al di fuori dell'intervallo dei valori dell'enumerazione, il valore risultante non è specificato. Esempio:

enum Color {
    RED = 1,
    GREEN = 2,
    BLUE = 3,
};
Color c = static_cast<Color>(4);

Tuttavia, nel prossimo esempio, il comportamento non è non specificato, poiché il valore di origine è compreso nell'intervallo dell'enumerazione, sebbene non sia uguale a tutti gli enumeratori:

enum Scale {
    ONE = 1,
    TWO = 2,
    FOUR = 4,
};
Scale s = static_cast<Scale>(3);

Qui s avrà il valore 3 e sarà disuguale a ONE , TWO e FOUR .

Cast statico dal valore di bogus void *

Se un valore void* viene convertito in un puntatore al tipo di oggetto, T* , ma non è allineato correttamente per T , il valore del puntatore risultante non è specificato. Esempio:

// Suppose that alignof(int) is 4
int x = 42;
void* p1 = &x;
// Do some pointer arithmetic...
void* p2 = static_cast<char*>(p1) + 2;
int* p3 = static_cast<int*>(p2);

Il valore di p3 non è specificato perché p2 non può puntare a un oggetto di tipo int ; il suo valore non è un indirizzo correttamente allineato.

Risultato di alcune reinterpret_cast conversioni

Il risultato di un reinterpret_cast da un tipo di puntatore a un altro, o un tipo di riferimento a un altro, non è specificato. Esempio:

int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
C ++ 03

Il risultato di un reinterpret_cast da un tipo di puntatore a un altro oggetto o un tipo di riferimento a un altro non è specificato. Esempio:

int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value

Tuttavia, con la maggior parte dei compilatori, questo era equivalente a static_cast<char*>(static_cast<void*>(&x)) quindi il puntatore risultante p puntava al primo byte di x . Questo è stato reso il comportamento standard in C ++ 11. Vedi la conversione della punteggiatura di tipo per maggiori dettagli.

Risultato di alcuni confronti tra puntatori

Se due puntatori vengono confrontati utilizzando < , > , <= o >= , il risultato non è specificato nei seguenti casi:

  • I puntatori indicano diversi array. (Un oggetto non array è considerato un array di dimensioni 1.)

    int x;
    int y;
    const bool b1 = &x < &y;            // unspecified
    int a[10];
    const bool b2 = &a[0] < &a[1];      // true
    const bool b3 = &a[0] < &x;         // unspecified
    const bool b4 = (a + 9) < (a + 10); // true
                                        // note: a+10 points past the end of the array
    
  • I puntatori indicano lo stesso oggetto, ma per i membri con controllo di accesso diverso.

    class A {
      public:
        int x;
        int y;
        bool f1() { return &x < &y; } // true; x comes before y
        bool f2() { return &x < &z; } // unspecified
      private:
        int z;
    };
    

Spazio occupato da un riferimento

Un riferimento non è un oggetto e, a differenza di un oggetto, non è garantito che occupi alcuni byte contigui di memoria. Lo standard lascia non specificato se un riferimento richiede alcuna memoria. Un certo numero di caratteristiche del linguaggio cospirano per rendere impossibile esaminare in maniera portabile qualsiasi memoria che il riferimento potrebbe occupare:

  • Se sizeof viene applicato a un riferimento, restituisce la dimensione del tipo di riferimento, senza fornire informazioni sul fatto che il riferimento occupi qualsiasi spazio di archiviazione.
  • Le matrici di riferimenti sono illegali, quindi non è possibile esaminare gli indirizzi di due elementi consecutivi di un ipotetico riferimento di matrici al fine di determinare la dimensione di un riferimento.
  • Se viene preso l'indirizzo di un riferimento, il risultato è l'indirizzo del referente, quindi non possiamo ottenere un puntatore al riferimento stesso.
  • Se una classe ha un membro di riferimento, il tentativo di estrarre l'indirizzo di quel membro usando offsetof produce un comportamento indefinito poiché tale classe non è una classe di layout standard.
  • Se una classe ha un membro di riferimento, la classe non è più un layout standard, quindi tenta di accedere a qualsiasi dato utilizzato per memorizzare i risultati del riferimento in un comportamento indefinito o non specificato.

In pratica, in alcuni casi una variabile di riferimento può essere implementata in modo simile a una variabile puntatore e quindi occupa la stessa quantità di memoria di un puntatore, mentre in altri casi un riferimento non può occupare spazio poiché può essere ottimizzato. Ad esempio, in:

void f() {
    int x;
    int& r = x;
    // do something with r
}

il compilatore è libero di trattare semplicemente r come alias per x e sostituire tutte le occorrenze di r nel resto della funzione f con x , e non allocare alcuna memoria per contenere r .

Ordine di valutazione degli argomenti della funzione

Se una funzione ha più argomenti, non è specificato in quale ordine vengono valutati. Il seguente codice potrebbe stampare x = 1, y = 2 o x = 2, y = 1 ma non specificato.

int f(int x, int y) {
    printf("x = %d, y = %d\n", x, y);
}
int get_val() {
    static int x = 0;
    return ++x;
}
int main() {
    f(get_val(), get_val());
}
C ++ 17

In C ++ 17, l'ordine di valutazione degli argomenti delle funzioni rimane non specificato.

Tuttavia, ogni argomento di funzione viene completamente valutato e l'oggetto chiamante è garantito valutato prima che gli argomenti di una funzione siano.

struct from_int {
  from_int(int x) { std::cout << "from_int (" << x << ")\n"; }
};
int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }


void foo(from_int a, from_int b) {
}
void bar(from_int a, from_int b) {
}

auto which_func(bool b){
  std::cout << b?"foo":"bar" << "\n";
  return b?foo:bar;
}

int main(int argc, char const*const* argv) {
  which_func( true )( make_int(1), make_int(2) );
}

questo deve stampare:

bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)

o

bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)

potrebbe non stampare la bar dopo una qualsiasi delle make o from , e potrebbe non stampare:

bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)

o simili. Prima della bar stampa C ++ 17 dopo che make_int s era legale, come facevano entrambi i make_int s prima di fare qualsiasi from_int s.

Spostato dallo stato della maggior parte delle classi di libreria standard

C ++ 11

Tutti i contenitori di libreria standard vengono lasciati in uno stato valido ma non specificato dopo essere stati spostati da. Ad esempio, nel codice seguente, v2 conterrà {1, 2, 3, 4} dopo lo spostamento, ma non è garantito che v1 sia vuoto.

int main() {
    std::vector<int> v1{1, 2, 3, 4};
    std::vector<int> v2 = std::move(v1);
}

Alcune classi hanno uno stato spostato con precisione definito. Il caso più importante è quello di std::unique_ptr<T> , che è garantito come null dopo essere stato spostato da.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow