Up | Home

C/C++ notes

Table of Contents

1. Introduction

These are some C and C++ notes that I have considered important throughout the years, although I will probably continue updating this list in the future. I will try to store the source of each note,

Here are some related resources that you might find interesting:

2. C notes

2.1. Arrays

2.1.1. Array subscripting

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero)1.

2.1.2. Contiguous elements without padding

Array elements have to be contiguous without padding between them2.

2.2. Structures

See also Bit-fields.

2.2.1. Order of structure members

The address of each structure member must increase in the order in which they are declared. There may be unnamed padding within a structure object, but not at its beginning3.

2.2.2. End padding in structures

There may be unnamed padding at the end of a structure or union4. This is normally done to align the start address of a potentially contiguous structure of the same type (e.g. when declaring arrays of this structure); see Contiguous elements without padding.

2.2.3. Alignment of each member

Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type5.

2.2.4. Flexible array members

The last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored, but it can still be used to access elements of an array of that specified type as if it was declared at that offset in the structure6.

For example, after the declaration:

struct Foo {
    int n;
    double d[]; /* Flexible array member */
};

The structure Foo has a flexible array member d. A typical way to use this is:

int m = /* some value */;
struct Foo* p = malloc(sizeof(struct Foo) + sizeof(double[m]));

And assuming that the call to malloc succeeds, the object pointed to by p behaves, for most purposes, as if p had been declared as:

struct {
    int n;
    double d[m];
}* p;

There are circumstances in which this equivalence is broken; in particular, the offsets of member d might not be the same.

2.3. Bit-fields

See also Structures.

2.3.1. Pointers to bit-field objects

The unary & (address-of) operator cannot be applied to a bit-field object; thus, there are no pointers to or arrays of bit-field objects7.

2.3.2. Order of allocation of bit-fields

The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined8. Therefore, in the following example:

struct Foo {
    uint32_t a : 3;
    uint32_t b : 11;
};

struct Foo foo;
foo.a = 0;     /* Unnecessary */
foo.b = 0x7FF; /* 0b11111111111 */

The layout of the foo variable might be any of the following (assuming the integer is stored in big-endian format):

| Binary                             |        Hex |   Decimal |
|------------------------------------+------------+-----------|
| 0b00011111111111000000000000000000 | 0x1FFC0000 | 536608768 |
| 0b00000000000000000011111111111000 |     0x3FF8 |     16376 |

2.3.3. Unnamed and zero width bit-fields

A bit-field declaration with no declarator, but only a colon and a width, indicates an unnamed bit-field9. Therefore, in the following example:

struct Foo {
    uint32_t a : 3;
    uint32_t   : 6;
    uint32_t b : 11;
};

struct Foo foo;
foo.a = 0x7;   /* 0b00000000111 */
foo.b = 0x7FF; /* 0b11111111111 */

The layout of the foo variable would be the following (assuming the integer is stored in big-endian format, and that the bit-fields are allocated low-order to high-order):

| Binary                             |     Hex | Decimal |
|------------------------------------+---------+---------|
| 0b00000000000011111111111000000111 | 0xFFE07 | 1048071 |

As a special case, an unnamed bit-field member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit-field, if any, was placed. Therefore, in the following example:

struct Foo {
    uint32_t a : 3;
    uint32_t b : 11;
    uint32_t   : 0;
    uint32_t c : 5;
};

struct Foo foo;
foo.a = 0x7;   /* 0b00000000111 */
foo.b = 0x7FF; /* 0b11111111111 */
foo.c = 0x1F;  /* 0b00000011111 */

The layout of the foo variable would be the following (again, assuming the integers are stored in big-endian format, and that the bit-fields are allocated low-order to high-order):

.............................###................................ (foo.a)
..................###########................................... (foo.b)
...........................................................##### (foo.c)
^0      ^1      ^2      ^3      ^4      ^5      ^6      ^7       (byte number)

`------------------------------´`------------------------------´
           First unit                     Second unit

Footnotes:

1

See C99, 6.5.2.1, point 2; and 6.5.6, point 8.

2

See C99, 6.5.6, points 8 and 9, and specially footnote 91.

3

See C99, 6.7.2.1, point 13.

4

See C99, 6.7.2.1, point 15.

5

See C99, 6.7.2.1, point 12; and C99, J.3.9, point 5.

6

This explanation is an oversimplification. See C99, 6.7.2.1, points 16 to 22.

7

See C99, 6.7.2.1, footnote 106.

8

See C99, 6.7.2.1, point 10.

9

See C99, 6.7.2.1, point 11.