Szukaj…


Uwagi

Jeśli zachowanie konstrukcji nie jest określone, wówczas standard nakłada pewne ograniczenia na zachowanie, ale pozostawia pewną swobodę implementacji, która nie jest wymagana do udokumentowania tego, co dzieje się w danej sytuacji. Kontrastuje z zachowaniem zdefiniowanym w ramach implementacji, w którym implementacja jest wymagana do udokumentowania tego, co się dzieje, oraz zachowaniem niezdefiniowanym, w którym wszystko może się zdarzyć.

Kolejność inicjalizacji globałów w TU

Podczas gdy w jednostce tłumaczeniowej określony jest porządek inicjalizacji zmiennych globalnych, kolejność inicjalizacji między jednostkami tłumaczeniowymi jest nieokreślona.

Więc program z następującymi plikami

  • foo.cpp

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

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

    int main() {}
    

może produkować jako wynik:

foobar

lub

barfoo

Może to prowadzić do statycznego zlecenia inicjalizacji Fiasco .

Wartość wyliczenia poza zakresem

Jeśli wyliczenie o zasięgu zostanie przekonwertowane na typ całkowy, który jest zbyt mały, aby pomieścić jego wartość, wynikowa wartość jest nieokreślona. Przykład:

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

Ponadto, jeśli liczba całkowita jest konwertowana na wyliczenie, a wartość liczby całkowitej jest poza zakresem wartości wyliczenia, wynikowa wartość jest nieokreślona. Przykład:

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

Jednak w następnym przykładzie zachowanie nie jest nieokreślone, ponieważ wartość źródłowa mieści się w zakresie wyliczenia, chociaż jest nierówna dla wszystkich podmiotów wyliczających:

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

Tutaj s będzie miało wartość 3 i będzie nierówne w ONE , TWO i FOUR .

Odlewanie statyczne z fałszywej wartości void *

Jeśli wartość void* jest konwertowana na wskaźnik do typu obiektu T* , ale nie jest odpowiednio wyrównana dla T , wynikowa wartość wskaźnika jest nieokreślona. Przykład:

// 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);

Wartość p3 jest nieokreślona, ponieważ p2 nie może wskazywać na obiekt typu int ; jego wartość nie jest właściwie wyrównanym adresem.

Wynik niektórych konwersji reinterpretacji

Wynik reinterpret_cast z jednego typu wskaźnika funkcji na inny lub jednego odwołania do funkcji na inny jest nieokreślony. Przykład:

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

Wynik reinterpret_cast z jednego typu wskaźnika obiektu na inny lub jednego typu odwołania do obiektu na inny jest nieokreślony. Przykład:

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

Jednak w przypadku większości kompilatorów było to równoważne z static_cast<char*>(static_cast<void*>(&x)) więc wynikowy wskaźnik p wskazywał na pierwszy bajt x . To stało się standardowym zachowaniem w C ++ 11. Zobacz konwersję typu punning, aby uzyskać więcej informacji.

Wynik niektórych porównań wskaźnika

Jeśli dwa wskaźniki są porównywane za pomocą < , > , <= lub >= , wynik nie jest określony w następujących przypadkach:

  • Wskaźniki wskazują różne tablice. (Obiekt niebędący tablicą jest uważany za tablicę o rozmiarze 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
    
  • Wskaźniki wskazują na ten sam obiekt, ale na elementy o różnej kontroli dostępu.

    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;
    };
    

Przestrzeń zajmowana przez odniesienie

Odwołanie nie jest obiektem i, w przeciwieństwie do obiektu, nie ma gwarancji, że zajmie jakieś ciągłe bajty pamięci. Standard pozostawia nieokreślone, czy odniesienie w ogóle wymaga przechowywania. Wiele funkcji języka konspiruje, aby uniemożliwić przenośne sprawdzenie pamięci, którą może zajmować odwołanie:

  • Zastosowanie sizeof do referencji powoduje zwrócenie rozmiaru referencyjnego typu, co nie daje informacji o tym, czy referencja zajmuje jakąkolwiek pamięć.
  • Tablice referencji są nielegalne, dlatego nie można badać adresów dwóch kolejnych elementów hipotetycznego odwołania tablic w celu ustalenia rozmiaru referencji.
  • Jeśli zostanie wzięty adres odwołania, wynikiem jest adres odwołania, więc nie możemy uzyskać wskaźnika do samego odwołania.
  • Jeśli klasa ma element referencyjny, próba wyodrębnienia adresu tego elementu za pomocą offsetof daje niezdefiniowane zachowanie, ponieważ taka klasa nie jest klasą o układzie standardowym.
  • Jeśli klasa ma element referencyjny, klasa nie jest już standardowym układem, więc próby uzyskania dostępu do danych używanych do przechowywania referencji powodują niezdefiniowane lub nieokreślone zachowanie.

W praktyce, w niektórych przypadkach zmienna referencyjna może być implementowana podobnie do zmiennej wskaźnikowej, a zatem zajmuje tyle samo miejsca co wskaźnik, podczas gdy w innych przypadkach referencja może nie zajmować w ogóle miejsca, ponieważ można ją zoptymalizować. Na przykład w:

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

kompilator może po prostu traktować r jako alias dla x i zastąpić wszystkie wystąpienia r w pozostałej części funkcji f x , i nie przydzielać żadnej pamięci do przechowywania r .

Kolejność oceny argumentów funkcji

Jeśli funkcja ma wiele argumentów, nie jest określone, w jakiej kolejności są obliczane. Poniższy kod może wypisać x = 1, y = 2 lub x = 2, y = 1 ale nie jest określone, w którym.

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

W C ++ 17 kolejność oceny argumentów funkcji pozostaje nieokreślona.

Jednak każdy argument funkcji jest w pełni oceniany, a obiekt wywołujący jest gwarantowany w ocenie przed argumentami funkcji.

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) );
}

to musi wydrukować:

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

lub

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

nie może drukować bar po którymś z make lub from „s, a to nie może wydrukować:

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

lub podobne. Przed C ++ 17 bar drukowania po make_int s był legalny, podobnie jak robienie obu make_int s przed wykonaniem jakiegokolwiek z from_int s.

Przeniesiono ze stanu większości standardowych klas bibliotek

C ++ 11

Wszystkie standardowe kontenery biblioteczne po przeniesieniu pozostają w prawidłowym, ale nieokreślonym stanie. Na przykład w poniższym kodzie v2 będzie zawierać {1, 2, 3, 4} po przeniesieniu, ale nie gwarantuje się, że v1 będzie pusta.

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

Niektóre klasy mają dokładnie zdefiniowany stan przeniesienia. Najważniejszym przypadkiem jest przypadek std::unique_ptr<T> , który gwarantuje przeniesienie z null po przeniesieniu.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow