C++
Ospecificerat beteende
Sök…
Anmärkningar
Om beteendet hos en konstruktion är ospecificerad lägger standarden vissa begränsningar för beteendet, men lämnar viss frihet till implementeringen, vilket inte krävs för att dokumentera vad som händer i en given situation. Det kontrasterar med genomförandet definierade beteende , där genomförandet krävs för att dokumentera vad som händer, och odefinierade beteende, där allt kan hända.
Initieringsordning för globaler över TU
Medan inuti en översättningsenhet specificeras ordningen för initialisering av globala variabler, specificeras ordningen för initialisering över översättningsenheter.
Så programmera med följande filer
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
kan producera som utgång:
foobar
eller
barfoo
Det kan leda till statisk initialiseringsorder Fiasco .
Värdet på en out-of-range enum
Om en scoped enum konverteras till en integrerad typ som är för liten för att hålla dess värde, är det resulterande värdet ospecificerat. Exempel:
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
Om ett heltal konverteras till ett enum och heltalets värde ligger utanför området för enumvärdena, är det resulterande värdet ospecificerat. Exempel:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Men i nästa exempel är beteendet inte ospecificerad, eftersom källvärdet är inom räckhåll för enum, även om det är olika för alla enumerators:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
Här s
kommer att ha värdet tre och vara olika till ONE
, TWO
och FOUR
.
Statisk gjutning från falskt tomrum * -värde
Om ett void*
-värde konverteras till en pekare till objekttyp, T*
, men inte är korrekt justerat för T
, är det resulterande pekarvärdet ospecificerat. Exempel:
// 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);
Värdet på p3
är inte specificerat eftersom p2
inte kan peka på ett objekt av typen int
; dess värde är inte en korrekt justerad adress.
Resultat av några omintolkade_cast-omvandlingar
Resultatet av en reinterpret_cast
från en funktionspekartyp till en annan, eller en funktionsreferenstyp till en annan, är inte specificerad. Exempel:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
Resultatet av en reinterpret_cast
från en objektpekartyp till en annan, eller en objektreferenstyp till en annan, är inte specificerad. Exempel:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Men för de flesta kompilatorer var detta ekvivalent med static_cast<char*>(static_cast<void*>(&x))
så den resulterande pekaren p
pekade på den första byten av x
. Detta gjordes som standardbeteende i C ++ 11. Se typ punningkonvertering för mer information.
Resultat av vissa pekarsjämförelser
Om två pekare jämförs med <
, >
, <=
eller >=
, är resultatet ospecificerat i följande fall:
Pekarna pekar på olika matriser. (Ett objekt utan array betraktas som en matris med storlek 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
Pekarna pekar på samma objekt, men till medlemmar med olika åtkomstkontroll.
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; };
Utrymme ockuperat av en referens
En referens är inte ett objekt, och till skillnad från ett objekt är det inte garanterat att uppta vissa sammanhängande byte av minne. Standarden lämnar det ospecificerat om en referens kräver någon lagring alls. Ett antal funktioner i språket konspirerar för att göra det omöjligt att portabelt undersöka lagring som referensen kan uppta:
- Om
sizeof
tillämpas på en referens, returnerar den storleken på den refererade typen och ger därmed ingen information om referensen upptar någon lagring. - Referenser är olagliga, så det är inte möjligt att undersöka adresserna för två på varandra följande element i en hypotetisk referens av matriser för att bestämma storleken på en referens.
- Om adressen till en referens tas är resultatet referensens adress, så vi kan inte få en pekare till själva referensen.
- Om en klass har en referensmedlem ger försök att extrahera adressen till den medlemmen med
offsetof
odefinierat beteende eftersom en sådan klass inte är en standardlayoutklass. - Om en klass har en referensmedlem är klassen inte längre standardlayout, så försöker få åtkomst till data som används för att lagra referensresultaten i odefinierat eller ospecificerat beteende.
I praktiken kan i vissa fall en referensvariabel implementeras på liknande sätt som en pekvariabel och därmed uppta samma mängd lagring som en pekare, medan i andra fall en referens inte kan uppta något utrymme alls eftersom det kan optimeras. Till exempel i:
void f() {
int x;
int& r = x;
// do something with r
}
kompilatorn är gratis att helt enkelt behandla r
som ett alias för x
och ersätta alla förekomster av r
i resten av funktionen f
med x
, och inte tilldela någon lagring för att hålla r
.
Utvärderingsordning för funktionsargument
Om en funktion har flera argument är det ospecificerat vilken ordning de utvärderas i. Följande kod kan skriva ut x = 1, y = 2
eller x = 2, y = 1
men det är ospecificerat vilket.
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());
}
I C ++ 17 förblir orden på utvärdering av funktionsargument ospecificerad.
Men varje funktionsargument utvärderas fullständigt, och det anropande objektet garanteras utvärderas innan det finns några funktionsargument.
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) );
}
detta måste skriva ut:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
eller
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
det får inte skriva ut bar
efter något av make
eller from
, och det får inte skriva ut:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
eller liknande. Före C ++ 17 tryckning bar
efter make_int
s var lagligt, som gjorde både make_int
s före att göra någon from_int
s.
Flyttad från tillståndet för de flesta standardbibliotekskurser
Alla standardbiblioteksbehållare lämnas i ett giltigt men ospecificerat tillstånd efter att de har flyttats från. Till exempel, i följande kod kommer v2
att innehålla {1, 2, 3, 4}
efter flytten, men v1
är inte garanterat att vara tom.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Vissa klasser har ett exakt definierat flyttat tillstånd. Det viktigaste fallet är det av std::unique_ptr<T>
, som garanteras att vara noll efter att ha flyttats från.