Właściwy sposób na zainicjowanie struktur C ++

82

Nasz kod zawiera strukturę POD (Plain Old Datastructure) (jest to podstawowa struktura c ++, która zawiera inne struktury i zmienne POD, które muszą zostać zainicjowane na początku).

Na podstawie tego, co przeczytałem , wydaje się, że:

myStruct = (MyStruct*)calloc(1, sizeof(MyStruct));

powinien zainicjować wszystkie wartości na zero, tak jak:

myStruct = new MyStruct();

Jednakże, gdy struktura jest inicjalizowana w drugi sposób, Valgrind później narzeka, że ​​„warunkowy skok lub ruch zależy od niezainicjowanych wartości”, gdy te zmienne są używane. Czy moje rozumienie jest tutaj błędne, czy też Valgrind rzuca fałszywe alarmy?

Cień503
źródło
1
Zwróć również uwagę, że new MyStruct()nie było wymagane ustawianie żadnych bajtów wypełniających w C ++ 03. W C ++ 0x jest. Wszelkie bity wypełnienia zostaną ustawione na 0 w C ++ 0x.
Johannes Schaub - litb
Dzięki za haczyk, zredagowałem moje pytanie.
Shadow503
Jak rozumiem, nie powinieneś otrzymywać tego komunikatu o błędzie. Być może Twoja struktura danych nie jest POD? Czy możesz zredukować swój kod do najmniejszego programu, który nadal wykazuje takie zachowanie i opublikować ten destylowany program? (Zobacz sscce.org ).
Robᵩ
Jaki kompilator? Czy możesz opublikować definicję MyStruct?
Alan Stokes

Odpowiedzi:

121

W C ++ klasy / struktury są identyczne (pod względem inicjalizacji).

Struktura inna niż POD może równie dobrze mieć konstruktora, dzięki czemu może inicjować członków.
Jeśli twoja struktura jest POD, możesz użyć inicjatora.

struct C
{
    int x; 
    int y;
};

C  c = {0}; // Zero initialize POD

Alternatywnie możesz użyć domyślnego konstruktora.

C  c = C();      // Zero initialize using default constructor
C  c{};          // Latest versions accept this syntax.
C* c = new C();  // Zero initialize a dynamically allocated object.

// Note the difference between the above and the initialize version of the constructor.
// Note: All above comments apply to POD structures.
C  c;            // members are random
C* c = new C;    // members are random (more officially undefined).

Myślę, że Valgrind narzeka, ponieważ tak właśnie działało C ++. (Nie jestem dokładnie pewien, kiedy C ++ został zaktualizowany z domyślną konstrukcją inicjalizacji zerowej). Najlepszym rozwiązaniem jest dodanie konstruktora, który inicjalizuje obiekt (konstrukcje są dozwolonymi konstruktorami).

Na marginesie:
wielu początkujących próbuje docenić init:

C c(); // Unfortunately this is not a variable declaration.
C c{}; // This syntax was added to overcome this confusion.

// The correct way to do this is:
C c = C();

Szybkie wyszukiwanie „Najbardziej irytującej analizy” zapewni lepsze wyjaśnienie niż ja.

Martin York
źródło
1
Czy masz odniesienie do tej inicjalizacji zerowej zmiennych w C ++? Ostatnio słyszałem, że nadal zachowuje się tak samo jak C. IIRC, kompilator MSVC nawet w pewnym momencie porzucił niezainicjowane elementy składowe inicjujące zero (zakładam, że ze względu na wydajność), co najwyraźniej spowodowało dość poważne problemy dla innych działów MS :)
Marc Mutz - mmutz
1
nie, C()ale wydaje się , że masz rację .
Marc Mutz - mmutz
4
Z klasą POD T, T()ma zawsze zero zainicjowany przez skalarne podobiekty. Nazwa inicjalizacji najwyższego poziomu została nazwana inaczej w C ++ 98 niż w C ++ 03 („inicjalizacja domyślna” a „inicjalizacja wartości”), ale efekt końcowy dla POD jest taki sam. Zarówno C ++ 98, jak i C ++ 03 miałyby wszystkie wartości równe zero.
Johannes Schaub - litb
1
@RockyRaccoon C c();niestety nie jest deklaracją zmiennej, ale raczej deklaracją funkcji (funkcja o nazwie „c”, która nie przyjmuje parametrów i zwraca obiekt typu C). Google: Most Vexing Parse
Martin York
1
@RockyRaccoon Różnica tutaj i pytanie, do którego odsyłasz w swoim komentarzu; polega na tym, że w tym przypadku w konstruktorze nie ma żadnych parametrów. Dlatego kompilatorowi trudno jest przeanalizować to i określić różnicę między deklaracją zmiennej a deklaracją funkcji do przodu. Wybiera deklarację funkcji do przodu. Jeśli potrzebujesz parametru w konstruktorze, kompilator zobaczy, że jest to deklaracja zmiennej bez żadnych problemów. C c(4);Jest prawidłową deklaracją zmiennej.
Martin York
2

Z tego, co nam powiedziałeś, wygląda na to, że valgrind jest fałszywie dodatni. newSkładnia z ()cenić zainicjować obiekt, zakładając, że jest POD.

Czy to możliwe, że część Twojej struktury nie jest w rzeczywistości POD, a to zapobiega oczekiwanej inicjalizacji? Czy jesteś w stanie uprościć swój kod do postaci wysyłanego przykładu, który nadal oznacza błąd Valgrind?

Alternatywnie, być może Twój kompilator w rzeczywistości nie inicjalizuje wartości struktur POD.

W każdym razie prawdopodobnie najprostszym rozwiązaniem jest napisanie konstruktora (-ów) zgodnie z potrzebami dla struktury / części podrzędnych.

Mark B.
źródło
1

Piszę kod testowy:

#include <string>
#include <iostream>
#include <stdio.h>

using namespace std;

struct sc {
    int x;
    string y;
    int* z;
};

int main(int argc, char** argv)
{
   int* r = new int[128];
   for(int i = 0; i < 128; i++ ) {
        r[i] = i+32;
   }
   cout << r[100] << endl;
   delete r;

   sc* a = new sc;
   sc* aa = new sc[2];
   sc* b = new sc();
   sc* ba = new sc[2]();

   cout << "az:" << a->z << endl;
   cout << "bz:" << b->z << endl;
   cout << "a:" << a->x << " y" << a->y << "end" << endl;
   cout << "b:" << b->x << " y" << b->y <<  "end" <<endl;
   cout << "aa:" << aa->x << " y" << aa->y <<  "end" <<endl;
   cout << "ba:" << ba->x << " y" << ba->y <<  "end" <<endl;
}

g ++ skompiluj i uruchom:

./a.out 
132
az:0x2b0000002a
bz:0
a:854191480 yend
b:0 yend
aa:854190968 yend
ba:0 yend
Yadong
źródło
Ok, nie jestem tak obeznany z new sc;vs new sc();. Pomyślałbym, że brakujące pareny to błąd. Ale wydaje się, że to pokazuje, że odpowiedź jest prawidłowa (
inicjuje
0

Musisz zainicjować wszystkich członków, których masz w swojej strukturze, np:

struct MyStruct {
  private:
    int someInt_;
    float someFloat_;

  public:
    MyStruct(): someInt_(0), someFloat_(1.0) {} // Initializer list will set appropriate values

};
ralphtheninja
źródło
W tym przypadku nie używam klasy, ale struct.
Shadow503
Struct to klasa.
Edytowałem
2
Nie musisz tego robić w przypadku dowodu dostawy
Alan Stokes
0

Ponieważ jest to struktura POD, zawsze możesz ustawić ją na 0 - może to być najłatwiejszy sposób na zainicjowanie pól (zakładając, że jest to właściwe).

Scott C Wilson
źródło
1
To, czy jest to struktura, czy klasa, nie ma nic wspólnego z możliwością tworzenia memsetu lub tworzenia konstruktora. Zobacz np. Stackoverflow.com/questions/2750270/cc-struct-vs-class
Erik
Normalnie nie ustawiłbyś klasy po jej utworzeniu, ponieważ jej konstruktor (prawdopodobnie) wykonał inicjalizację.
Scott C Wilson
1
nie ma różnicy między „class” i „struct” w C ++, z wyjątkiem domyślnej ochrony (odpowiednio prywatnej i publicznej). Struktura może nie być POD, a klasa może być POD, te pojęcia są ortogonalne.
Marc Mutz - mmutz
@mmutz i @Erik - zgodzili się - skrócili moją odpowiedź, aby uniknąć nieporozumień.
Scott C Wilson
memset () nie jest najlepszą opcją, możesz wyzerować strukturę POD poprzez wywołanie jej domyślnego ctora.
Nils
0

Wydaje mi się, że to najłatwiejszy sposób. Składowe struktury można zainicjować za pomocą nawiasów klamrowych „{}”. Na przykład poniżej przedstawiono prawidłową inicjalizację.

struct Point 
{ 
   int x, y; 
};  

int main() 
{ 
   // A valid initialization. member x gets value 0 and y 
   // gets value 1.  The order of declaration is followed. 
   struct Point p1 = {0, 1};  
}

Istnieją dobre informacje o strukturach w języku c ++ - https://www.geeksforgeeks.org/structures-in-cpp/

Michael Klishevich
źródło