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:
- C99 standard: https://www.iso-9899.info/n1256.html
- Dennis Yurichev’s C notes: https://yurichev.com/C-book.html
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:
See C99, 6.5.2.1, point 2; and 6.5.6, point 8.
See C99, 6.5.6, points 8 and 9, and specially footnote 91.
See C99, 6.7.2.1, point 13.
See C99, 6.7.2.1, point 15.
See C99, 6.7.2.1, point 12; and C99, J.3.9, point 5.
This explanation is an oversimplification. See C99, 6.7.2.1, points 16 to 22.
See C99, 6.7.2.1, footnote 106.
See C99, 6.7.2.1, point 10.
See C99, 6.7.2.1, point 11.