Showing posts with label cpp_pod. Show all posts
Showing posts with label cpp_pod. Show all posts

Apr 26, 2014

[C++] struct / class not only different in access level, but in size through inheritance , because of POD

Output of the following code:
4,4,6  2,2,2

POD or non-POD affect the layout size.
 
#include <iostream>

struct S {
   short a;
   char b;
};

struct ES : S {
   char c;
};

class C {
   short a;
   char b;
};

class EC : C {
   char c;
};



int main() {
   std::cout << sizeof(C) << std::endl;
   std::cout << sizeof(EC) << std::endl;
   std::cout << sizeof(ES) << std::endl;
   std::cout << "alignof(C) " << alignof(C) << std::endl;
   std::cout << "alignof(EC) " << alignof(EC) << std::endl;
   std::cout << "alignof(ES) " << alignof(ES) << std::endl;


   return 0;
}
Reference:
The Lost Art of C Structure Packing by Eric S. Raymond
Access specifiers (public/private/protected) don't affect inherited "object size" in anyway.?? not quite

Mar 9, 2014

[C++11] POD type

POD Type
Answer from stackoverflow

An aggregate is an

  • array 
  • class [i.e classes, structs, and unions] (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).
i.e:
  • Can have trivial constructor (not defined by user)
  • No private or protected non-static data members.
    • Can have member function
    • Can hava static member function
  • An aggregate class can have a user-declared/user-defined copy-assignment operator and/or destructor
  • An array is an aggregate even if it is an array of non-aggregate class type.
Type array_name[n] = {a1, a2, ..., am};

if(m == n)
//the ith element of the array is initialized with ai
else if(m < n)
//the first m elements of the array are initialized with 
//a1, a2, ..., am and the other n - m elements are,
//if possible, value-initialized (see below for the explanation of the term)
else if(m > n)
//the compiler will issue an error
else //(this is the case when n isn't specified at all like int a[] = {1,2,3};)
//the size of the array (n) is assumed to be equal to m, so 
int a[] = {1,2,3} 
//is equivalent to int a[3] = {1,2,3};
class A
{
public:
  A(int){} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1,2,3,4}; //ERROR, too many initializers
}


A POD-struct is

  • aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference
  • has no user-defined copy assignment operator 
  • no user-defined destructor. 
  • Similarly, a POD-union is 
    • an aggregate union that has no non-static data members of type non-POD-truct, non-POD-union (or array of such types) or reference
    • has no user-defined copy assignment operator 
    • no user-defined destructor. 
  • A POD class is a class that is either a POD-struct or a POD-union.
struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1(){} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};


POD-classes, POD-unions, scalar types, and arrays of such types are collectively called POD-types.

  • POD-classes are the closest to C structs. 
  • Unlike C struct, POD's can have member functions and arbitrary static members, but neither of these two change the memory layout of the object. 
  • If want to write a more or less portable dynamic library that can be used from C and even .NET, should try to make all exported functions take and return only parameters of POD-types.
  • The lifetime of POD object begins when storage for the object is occupied and finishes when that storage is released or reused.
  • For objects of POD types it is guaranteed by the standard that when you memcpy the contents of object into an array of char or unsigned char, and then memcpy the contents back into object, the object will hold its original value. 
  • Do note that there is no such guarantee for objects of non-POD types. 
  • Also, we can safely copy POD objects with memcpy.
#define N sizeof(T)
char buf[N];
T obj; // obj initialized to its original value
memcpy(buf, &obj, N); // between these two calls to memcpy,
// obj might be modified
memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
// holds its original value


goto statement:

  • Illegal (the compiler should issue an error) to make a jump via goto from a point where some NON-POD variable was not yet in scope to a point where it is already in scope.
//ill form
int f() {
  struct NonPOD { NonPOD(){}};
  goto label;
  NonPOD x;
label:
  return 0;
}

// OK
int g(){
  struct POD {int i;  char c;};
  goto label;
  POD x;
label:
  return 0;
}

Why can't variables be declared in a switch statement?

Guaranteed that there will be no padding in the beginning of a POD object.
In other words, if a POD-class A's first member is of type T,
we can safely reinterpret_cast from A* to T* and get the pointer to the first member and vice versa.


switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Jan 12, 2014

[C++ / C][NOTE] The Lost Art of C Structure Packing by Eric S. Raymond

https://maskray.me/blog/2026-02-22-bit-field-layout

ANSI C provides an offsetof() macro which can be used to read out structure member offsets Storage for the basic C datatypes on an x86 or ARM processor doesn’t normally start at arbitrary byte addresses in memory. Each type except char has an alignment requirement: * chars can start on any byte address they’re equally expensive from anywhere they live inside a single machine word. That’s why they don’t have a preferred alignment. * 2-byte shorts must start on an even address * 4-byte ints or floats must start on an address divisible by 4 * 8-byte longs or doubles must start on an address divisible by 8 * Signed or unsigned makes no difference. * basic C types on x86 and ARM are self-aligned * Pointers, whether 32-bit (4-byte) or 64-bit (8-byte) are self-aligned Can coerce compiler into not using the processor’s normal alignment rules by using a pragma usually #pragma pack. * Do not do this casually, as it forces the generation of more expensive and slower code. Pointer alignment - the strictest possible:

char *p;

char *p;      /* 4 or 8 bytes */
char c;       /* 1 byte */
char pad[3];  /* 3 bytes */
int x;        /* 4 bytes */

//----
char *p;      /* 4 or 8 bytes */
char c;       /* 1 byte */
char pad[1];  /* 1 byte */
short x;      /* 2 bytes */

//----
char *p;     /* 8 bytes */
char c;      /* 1 byte
char pad[7]; /* 7 bytes */
long x;      /* 8 bytes */

//----

char c;
char pad1[M]; //M unpredicable
char *p;
char pad2[N]; //N is 0
int x;

//---- Make predicable ----
char *p;     /* 8 bytes */
long x;      /* 8 bytes */
char c;      /* 1 byte

In general, a struct instance will have the alignment of its widest scalar member. Compilers do this as the easiest way to ensure that all the members are self-aligned for fast access.

struct foo1 {
    char *p;     /* 8 bytes */
    char c;      /* 1 byte
    char pad[7]; /* 7 bytes */
    long x;      /* 8 bytes */
}

//--- locked in padding , unlike non-struct variables---
struct foo2 {
    char c;      /* 1 byte */
    char pad[7]; /* 7 bytes , predicable, since foo2 is considered a 
    * 'variable strucure' , c always starts at first byte boundry */
    char *p;     /* 8 bytes */
    long x;      /* 8 bytes */
};

on a 64-bit x86 or ARM machine:

struct foo3 {
    char *p;     /* 8 bytes */
    char c;      /* 1 byte */
    /* trailing padding with 7 bytes*/
};

struct foo3 singleton; // sizeof(singleton); is 16 bytes
struct foo3 quad[4];

//------
struct foo4 {
    short s;     /* 2 bytes */
    char c;      /* 1 byte */
     /* trailing padding with 1 byte*/
};  //sizeof(foo4); is 4 bytes

//------

struct foo5 {
    short s;       /* 2 bytes */
    // ----- there's no 3 bytes padding after char c.
    // Continue with bit fields. Padding are at last
    char c;        /* 1 byte */
    int flip:1;    /* total 1 bit */
    int nybble:4;  /* total 5 bits */
    int septet:7;  /* total 12 bits */
    int pad1:4;    /* total 16 bits = 2 bytes */
    char pad2;     /* 1 byte */
};

//------

struct foo6 {
    char c;           /* 1 byte*/
    char pad1[7];     /* 7 bytes */
    struct foo6_inner {
        char *p;      /* 8 bytes, inner struct's data member forces 
         * outter to sync with largest alignment */
        short x;      /* 2 bytes */
        char pad2[6]; /* 6 bytes */
    } inner;
};
Rule of thumb: Make all the pointer-aligned subfields come first, because on a 64-bit machine they will be 8 bytes. Then the 4-byte ints; then the 2-byte shorts; then the character fields. e.g:

struct foo7 {
    char c;         /* 1 byte */
    char pad1[7];   /* 7 bytes */
    struct foo7 *p; /* 8 bytes */
    short x;        /* 2 bytes */
    char pad2[6];   /* 6 bytes */
};

//--- to ----
struct foo8 {
    struct foo8 *p;
    short x;
    char c;
};

//excerpt Using enumerated types instead of #defines is a good idea, if only because symbolic debuggers have those symbols available and can show them rather than raw integers. But, while enums are guaranteed to be compatible with an integral type(i.e In C), the C standard does not specify which underlying integral type is to be used for them. (In C++11, we could specify enum underlying type) Be aware when repacking your structs that while enumerated-type variables are usually ints, this is compiler-dependent; they could be shorts, longs, or even chars by default. Your compiler may have a pragma or command-line option to force the size. The long double type is a similar trouble spot. Some C platforms implement this in 80 bits, some in 128, and some of the 80-bit platforms pad it to 96 or 128 bits. In both cases it’s best to use sizeof() to check the storage size. Finally, under x86 Linux doubles are sometimes an exception to the self-alignment rule: An 8-byte double may require only 4-byte alignment within a struct even though standalone doubles variables have 8-byte self-alignment. This depends on compiler and options.