1. Home
  2. Tutorials
  3. C/C++
  4. C++ Data Structures
Yolinux.com Tutorial

C++ Data Structure and Union Tutorial

The C++ compiler treats the C data structure like a C++ class. This allows one to use C++ constructs with a "struct" when using the C++ compiler. This is also true for a "C" union with a constructor. Sample code examples with explanations and tips are discussed. All examples use the GNU g++ compiler (4.6.3) on Linux.



Free Information Technology Magazines and Document Downloads
TradePub link image

Free Information Technology Software and Development Magazine Subscriptions and Document Downloads

C++ Structure:

C++ introduces some differences and extensions from the classic C struct as well as commonality and differences with the C++ class. The traditional C struct is a grouping of variables in memory with no extra capabilities. C++ allows for some extended capabilities to the struct which were once the domain of the C++ class. This includes contructor/destructor and member functions.

C++ Struct Examples:

Simple example of a program using C++, a structure and a constructor:

// File: struct-test.cpp
//
// This example shows the use of a structure in C++ and how it behaves much
// like a class including the use of a contructor yet maintains the useability
// of a regular C structure.

#include <iostream>
#include <string>

using namespace std;

main()
{
    struct DataElement {
       string SVal;
       int    iVal;
       bool   hasData;

       DataElement()   // Example of a constructor used in a structure.
       {
          iVal=-1;
          hasData=0;
       }
    } *RealData;

    RealData = new DataElement [ 5 ];

    // Assignment
    RealData[0].SVal = "Value loaded into first structure element.";
    RealData[0].hasData = 1; // True

    cout << "First element  0: " << RealData[0].SVal << endl;
    cout << "                  " << RealData[0].hasData << endl;
    cout << "Second element 1: " << RealData[1].SVal << endl;
    cout << "                  " << RealData[1].hasData << endl; // Show effect of contructor
    cout << "                  " << RealData[1].iVal    << endl; // Show effect of contructor

    delete [] RealData;   // Or:  delete [5] RealData;

}

Compile: g++ struct-test.cpp

[Potential Pitfall]: In Red Hat Linux versions 7.x one could omit the "using namespace std;" statement. Use of this statement is good programming practice and is required in Red Hat 8.0.

[Potential Pitfall]: Red Hat 8.0 requires the reference to "#include <fstream>". Red Hat versions 7.x used "#include <fstream.h>".

Output: ./a.out

First element  0: Value loaded into first structure element.
                  1
Second element 1: 
                  0
                  -1

The same example using typedef and a C++ initializer:

#include <iostream>
#include <string>
using namespace std;

typedef struct dataElement {
   string SVal;
   int    iVal;
   bool   hasData;

   dataElement()   // Example of a constructor used in a structure.
      : iVal(-1), hasData(0)
   {}
} DataElement;

main()
{
    DataElement *RealData;
    RealData = new DataElement [ 5 ];

    RealData[0].SVal = "Value loaded into first structure element.";
    RealData[0].hasData = 1; // True

    cout << "First element  0: " << RealData[0].SVal << endl;
    cout << "                  " << RealData[0].hasData << endl;
    cout << "Second element 1: " << RealData[1].SVal << endl;
    cout << "                  " << RealData[1].hasData << endl; // Show effect of contructor
    cout << "                  " << RealData[1].iVal    << endl; // Show effect of contructor

    delete [] RealData;   // Or:  delete [5] RealData;
}

Identical results as above example.

By contrast the typical "C" structure initialization is as follows:

#include <stdio.h>

typedef struct dataElement {
   char   *cVal;
   int    iVal;
} DataElement;

main()
{
    DataElement RealData = {"Text goes here", 5 };  // "C" style initialization.

    printf("%s \nInteger value=%d\n",RealData.cVal,RealData.iVal);
}
Compile: gcc struct-simple.c -o gcc struct-simple
Run: ./struct-simple
Text goes here
Integer value=5

C++ structure notes:

  • The struct can employ a constructor to initalize variables.
  • The struct can employ a destructor.
  • The structure constructor can not be declared virtual.
  • Structure member variables are public by default.

C struct vs C++ struct vs C++ class:

When using the GNU C++ compiler, the C++ struct and class behave the same. While the C struct using an ANSI C compiler supports only variables, the GNU C++ (g++) compiler will support struct member functions, even virtual functions as well as public and private access controls and inheritance.

Features
ANSI C
struct
C++
struct
C++
class
member variablesyesyesyes
member functionsnoyesyes
constructor supportnoyesyes
destructor supportnoyesyes
access controlnoyesyes
default accesspublicpublicprivate
inheritancenoyesyes

The only difference between a C++ struct and a C++ class is that a struct defaults members to public while a class defaults to private.

struct ABC {
  int x;
};
main()
{
   ABC abc;
   abc.x = 5;
}
class ABC {
  int x;
};
main()
{
   ABC abc;
   abc.x = 5;
}
$ g++ test.cpp
$
$ g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:2:7: error: ‘int ABC::x’ is private
test.cpp:7:8: error: within this context

C++ struct and Virtual Functions:

In memory, the C++ struct will behave just like the C struct until a virtual function is used, at which point the compiler will prefix the struct's memory with a pointer to the virtual table. This is important especially when reading and writing structs across the network or to binary files as this prefixed pointer may have unintended consequences. Also a pointer to the struct with a virtual function will not be a pointer to the first variable in the struct as the first element in the struct will be the pointer to the virtual table. Also note that this pointer will reflect the word size of the hardware. To mimic the ANSI C behavior of a struct, a C++ struct with a virtual function will require a serialization process to be programmed into the computional logic and flow or avoid the use of virtual functions.

Compilers other than GNU C++ are free to implement the memory layout differently, so proper serialization of data will be required before exchanging a C++ struct or object with other systems. It is also permissible that other systems may rearrange the order of variables based on private/protected/public access definitions.

Memory order:
  • A pointer to the virtual functions table is added only when the class has virtual methods. Pointer size will be dependent on hardware and software word size. A 32 bit system will prefix a 32 bit pointer to the vtable while a 64 bit system will prefix a 64 bit pointer to the vtable.
  • Base class variable data
  • Class member variable data

The following example shows the memory layout of an object with class inheritance:

#include <iostream>
using namespace std;

struct AAA {
public:
  int aaa;
  AAA():aaa(0xff){};
  AAA(int _aaa){aaa = _aaa;}
  void printThis(){ cout << "     Object this pointer memory:            " << hex << (size_t)this << endl;}
};
struct BBB : AAA {
  int bbb;
  BBB():bbb(0x0000){};
  BBB(int _aaa, int _bbb){aaa=_aaa;bbb=_bbb;};
};

main()
{
    AAA aaa(0xffff);;
    BBB bbb;

    // AAA Print pointers - memory address of object and first member of object
    cout << "AAA: " << sizeof(AAA) << " bytes." << endl;
    cout << "     Object memory address location &aaa:   " << hex << (size_t)&aaa << endl;
    cout << "     First member variable Memory aaa.aaa:  " << hex << (size_t)&aaa.aaa << endl;
    aaa.printThis();  // Print object this pointer

    cout << "BBB: " << sizeof(BBB) << " bytes." << endl;
    cout << "     Object memory address location &bbb:   " << hex << (size_t)&bbb << endl;
    cout << "     First inherited member memory bbb.aaa: " << hex << (size_t)&bbb.aaa << endl;
    cout << "     First member variable Memory bbb.bbb:  " << hex << (size_t)&bbb.bbb << endl;
    bbb.printThis();
}
Run results:
AAA: 4 bytes.
     Object memory address location &aaa:   7fffe3a8c410
     First member variable Memory aaa.aaa:  7fffe3a8c410
     Object this pointer memory:            7fffe3a8c410
BBB: 8 bytes.
     Object memory address location &bbb:   7fffe3a8c400
     First inherited member memory bbb.aaa: 7fffe3a8c400
     First member variable Memory bbb.bbb:  7fffe3a8c404  -- BBB begins after AAA which 4 bytes in size
     Object this pointer memory:            7fffe3a8c400
The beginning of BBB memory is occupied by AAA from which it inherits data variables.

The following example shows the memory layout effect of adding a virtual function and the accompanying vtable pointer prepended to the object's memory footprint:

#include <iostream>
using namespace std;

struct AAA {
public:
  int aaa;
  AAA():aaa(0xff){};
  AAA(int _aaa){aaa = _aaa;}
  void printThis(){ cout << "     Object this pointer memory:            " << hex << (size_t)this << endl;}
  virtual void functionA(){};
};
struct BBB : AAA {
  int bbb;
  BBB():bbb(0x0000){};
  BBB(int _aaa, int _bbb){aaa=_aaa;bbb=_bbb;};
  void functionA(){};
};

main()
{
    AAA aaa(0xffff);;
    BBB bbb;

    // AAA Print pointers - memory address of object and first member of object
    cout << "AAA: " << sizeof(AAA) << " bytes." << endl;
    cout << "     Object memory address location &aaa:   " << hex << (size_t)&aaa << endl;
    cout << "     First member variable Memory aaa.aaa:  " << hex << (size_t)&aaa.aaa << endl;
    aaa.printThis();  // Print object this pointer

    cout << "BBB: " << sizeof(BBB) << " bytes." << endl;
    cout << "     Object memory address location &bbb:   " << hex << (size_t)&bbb << endl;
    cout << "     First inherited member memory bbb.aaa: " << hex << (size_t)&bbb.aaa << endl;
    cout << "     First member variable Memory bbb.bbb:  " << hex << (size_t)&bbb.bbb << endl;
    bbb.printThis();
}
Run results:
AAA: 16 bytes.
     Object memory address location &aaa:   7fff4b1d7fb0
     First member variable Memory aaa.aaa:  7fff4b1d7fb8  -- 8 bytes offset for vtable pointer
     Object this pointer memory:            7fff4b1d7fb0
BBB: 10 bytes.
     Object memory address location &bbb:   7fff4b1d7fc0
     First inherited member memory bbb.aaa: 7fff4b1d7fc8  -- 8 byte offset for vtable pointer
     First member variable Memory bbb.bbb:  7fff4b1d7fcc  -- 4 bytes additional offset for actual size of AAA
     Object this pointer memory:            7fff4b1d7fc0
Note that sizeof() does not work properly when used on a struct with a virtual function (and thus with a vtable pointer).

Note that the starting memory address of the object is not the same as that for the first member variable. This is due to the vtable pointer which consumes 8 bytes for the pointer on a 64 bit system. The position of the begining variables has shifted by these 8 bytes.

C++ Union example:

The following example shows how a "C" union can be used with a constructor to initialize data.

#include <stdio.h>

typedef union uAA
{
   double dVal;
   int iVal[2];

   uAA() : dVal(3.22) {}
} UAA;

main()
{
    UAA rdata;

    printf("Array output: %d %d \nDouble output: %lf \n",
           rdata.iVal[0], rdata.iVal[1], rdata.dVal);
}
Compile: g++ union-test.cpp -o union-test
Run: ./union-test
Array output: 1546188227 1074381455
Double output: 3.220000

[Potential Pitfall]: The C++ difference - Structures used with union. A struct can NOT be used with a constructor if it is to be used in a union. The C++ recognition of the constructor in a struct converts it to a C++ class which is not valid in a "C" union.

Typical use of a "C" union:
#include <stdio.h>

typedef struct
{
   int    iVal1;
   int    iVal2;
} DataElement;


typedef union
{
   DataElement de;
   int iVal[2];
} UAA;

main()
{
    UAA rdata;

    rdata.de.iVal1 = 0;
    rdata.de.iVal2 = 1;

    printf("Array output: %d %d \n", 
           rdata.iVal[0], rdata.iVal[1]);
}
Compile: gcc -o union-test union-test.c
Note: Compiles properly with the gcc and g++ compiler.
Run: ./union-test
Array output: 0 1
Use of C++ structure (with constructor) in a union:
#include <stdio.h>

typedef struct dataElement
{
   int    iVal1;
   int    iVal2;
   dataElement() : iVal1(1), iVal2(2){};
} DataElement;

typedef union
{
   DataElement de;
   int iVal[2];
} UAA;

main()
{
    UAA rdata;

    printf("Array output: %d %d \n",
           rdata.iVal[0], rdata.iVal[1]);
}
   
   
   
Compile: g++ -o union-test union-test.cpp

union-test.cpp:13: error: member `DataElement ::de' with constructor not allowed in union

C++ union notes:

  • The union can employ a constructor to initalize variables.
  • The union can employ a destructor.
  • Union member functions can NOT be declared virtual.
  • Union member variables are public by default.
  • A union's data members can NOT be declared static.
  • A union can not be used as a base class.

Note that structures can have endian byte order issues depending on the hardware used. See Endian Byte Order, big vs little endian and byte swapping for more information.

Why a tutorial on the seemingly simple subject of structs and unions? While it at first may appear simple, the comlete coverage has been overlooked by every C++ book I have ever seen. Many programmers I have met didn't know that constructors could be used with a C structure. I didn't know until I was told by another programmer. It was not covered by my professor nor was it in our very thourough C++ text book. This is just an FYI.

Books:

C++ How to Program
by Harvey M. Deitel, Paul J. Deitel
ISBN #0131857576, Prentice Hall

Fifth edition. The first edition of this book (and Professor Sheely at UTA) taught me to program C++. It is complete and covers all the nuances of the C++ language. It also has good code examples. Good for both learning and reference.

Amazon.com

   
Bookmark and Share

Advertisements