Skip to content

Latest commit

 

History

History
1081 lines (914 loc) · 75.1 KB

cpp_design_bookmarks.md

File metadata and controls

1081 lines (914 loc) · 75.1 KB

The Code-Reviewer's Notes

About the author and how to get in touch.

The unsorted fragments of knowledge to support my notes during the code reviews (and the bookmarks for my own reference).



Type Sizes And Other Peculiarities

[C11_N1570]

bool
6.2.5 Types, paragraph 2

An object declared as type _Bool is large enough to store the values 0 and 1.

I.e. the size of _Bool is at least one bit, but there is no upper limit for the size of _Bool. TODO: Correspondence between bool and _Bool in C.

char
6.5.3.4 The sizeof and _Alignof operators, paragraph 4:

When sizeof is applied to an operand that has type char, unsigned char, or signed char, (or a qualified version thereof) the result is 1.

6.2.5 Types, paragraph 15:

The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char.45)

45) CHAR_MIN, defined in <limits.h>, will have one of the values 0 or SCHAR_MIN, and this can be used to distinguish the two options. Irrespective of the choice made, char is a separate type from the other two and is not compatible with either.

6.3.1.1 Boolean, characters, and integers, Paragraph 3

whether a "plain" char is treated as signed is implementation-defined.

Integer Types
5.2.4.2.1 Sizes of integer types <limits.h>
Important sentence:

Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

I.e. the values shown below are the lower limits in magnitude (the limits closest to 0). The actual values can be greater in magnitude (further away from 0).

— minimum value for an object of type short int
SHRT_MIN -32767 // −(2^15 − 1)
— maximum value for an object of type short int
SHRT_MAX +32767 // 2^15 − 1
— maximum value for an object of type unsigned short int
USHRT_MAX 65535 // 2^16 − 1
— minimum value for an object of type int
INT_MIN -32767 // −(2^15 − 1)
— maximum value for an object of type int
INT_MAX +32767 // 2^15 − 1
— maximum value for an object of type unsigned int
UINT_MAX 65535 // 2^16 − 1
— minimum value for an object of type long int
LONG_MIN -2147483647 // −(2^31 − 1)
— maximum value for an object of type long int
LONG_MAX +2147483647 // 2^31 − 1
— maximum value for an object of type unsigned long int
ULONG_MAX 4294967295 // 2^32 − 1
— minimum value for an object of type long long int
LLONG_MIN -9223372036854775807 // −(2^63 − 1)
— maximum value for an object of type long long int
LLONG_MAX +9223372036854775807 // 2^63 − 1
— maximum value for an object of type unsigned long long int
ULLONG_MAX 18446744073709551615 // 2^64 − 1

6.2.5 Types
Paragraph 5

A "plain" int object has the natural size suggested by the architecture of the execution environment (large enough to contain any value in the range INT_MIN to INT_MAX as defined in the header <limits.h>).

Paragraph 6

For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned) that uses the same amount of storage (including sign information) and has the same alignment requirements.

Paragraph 8

For any two integer types with the same signedness and different integer conversion rank (see 6.3.1.1), the range of values of the type with smaller integer conversion rank is a subrange of the values of the other type.

6.3.1.1 Boolean, characters, and integers
Paragraph 1

— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
— The rank of _Bool shall be less than the rank of all other standard integer types.
— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).

Conclusion
{ sizeof(short int), sizeof(int) } >= 2
{ sizeof(unsigned short int), sizeof(unsigned int) } >= 2
sizeof(long int) >= 4
sizeof(unsigned long int) >= 4
sizeof(long long int) >= 8
sizeof(unsigned long long int) >= 8

Identifiers

Some categories of identifiers are reserved by the C++ standard. See
[C++98] 17.4.3.1.2 Global names,
[C++17_N4659] 5.10 Identifiers, paragraphs 3.1 and 3.2

  • Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
  • Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.

If your code uses such identifiers (e.g. __MY_HEADER_H_INCLUDED, __my_local_var, _MyMemberVar, _myGlobalVar) then upon next edition of the standard (i.e. upon migration to the next version of your compiler) your code can start behaving in an unexpected/unpredictable way.

Booleans

Prefer bool For Booleans

The bool type has not been included in the [C89]/[C90] standard. For a number of years prior to that and for almost a decade after that all the C and C++ programmers were using their own implementation of the Boolean type. Then starting with [C++98] the bool type has been standardized in C++. Shortly after that starting with [C99] it has been standardized in C. But having a large code base and a strong habit people were still using their own implementation. The situation was also facilitated by the fact that not all the compilers were supporting bool, e.g. Visual Studio started supporting bool (to be more precise, started providing <stdbool.h>) in C with the version of 2015 (or 2012, double-check). In such a situation of having bool in C++ and not having in C for a long time, and partially for code portability between C++ and C, many still are using own Boolean implementations.
Although bool type has been standardized both in C and C++ for nearly two decades now, in C it still requires including <stdbool.h> whereas in C++ it is a language keyword and does not require anything extra. Thus the support is still suboptimal. None the less the type bool (and its values false and true) still has its advantages over the other alternatives of Boolean.

  • The type bool is standard both in C and C++. It does not cause any questions for those who know about it. Whereas such types as BOOL or Bool or other Boolean implementations cause questions (e.g. "What is the difference of this type from bool?") and typically force to take a look at the implementation in order to understand the difference from bool and limitations originating from that.
  • When converting a number of types to bool the Boolean conversions guarantee that the bool will take one of the two values - either false or true, whereas converting to other implementations can result in more than two values, e.g. converting value 5 to some implementation, let’s say named BOOL, can result in a value not equal to FALSE and not equal to TRUE. If that implementation is based on the unsigned integer type then the behavior will still be defined. But if the implementation is based on the signed integer type then the behavior can become undefined if the conversion overflows that signed interger type. Also (as Alexander Zaitsev has informed) if the implementation is the enum type, e.g. typedef enum { FALSE, TRUE } BOOL; then the resulting value not equal to FALSE and not equal to TRUE is unspecified until [C++17] and causes the undefined behavior since [C++17] (search for "unspecified" in the Enumeration declaration).
    Andrey Karpov has also admitted, the conversion of any (raw) pointer to bool is consistent (any non-null pointer is converted to true) whereas the conversion to other implementations can be inconsistent, e.g. converting non-null 16-bit pointer with 8 least significant bits equal to 0 (e.g. 0xAB00) to a 8-bit Boolean implementation can result in a value of 0 equivalent to false (i.e.
    converting the non-null pointer 0xAB00 results in the equivalent of false (0x00), but
    converting the non-null pointer 0xAB01 results in the equivalent of true (0x01)).
  • If you use some type (e.g. int) for your Boolean implementation then you will not be able to provide two distinct function overloads, one for that type (int) and one for Boolean, e.g. like this
void f(int);
void f(BOOL);

See Also:

  • V721. The VARIANT_BOOL type is utilized incorrectly. The true value (VARIANT_TRUE) is defined as -1 (+RU).

What to Remember:

For Booleans prefer using the type bool and its values false and true.

How to Automate Catching This:

Help is wanted...

PVS-Studio:

  • V724. Converting integers or pointers to BOOL can lead to a loss of high-order bits. Non-zero value can become 'FALSE' (+RU).

How to Force the Bugs to Show Themselves:

Help is wanted...

Avoid Comparing Booleans to true

Comparing a Boolean value to true is unreliable.

Sometimes there are cases when the integer value 0 acts as an indication of false. And an arbitrary non-zero integer value acts as an indication of true (e.g. such a value can be returned by a function). Comparing the (true-like) non-zero integer value to true (or to the user-defined constant TRUE) is likely to fail.

If such an integer value (with Boolean-like behavior) is assigned to a bool variable then the Boolean conversions guarantee that the bool will still stay false (0) or true (1). However the programmers sometimes do such tricks that the unexpected value still penetrates into the bool variable. E.g. they copy the Boolean-like integer byte-by-byte into the bool. This results in an arbitrary non-zero value, acting as true, to be in the bool variable instead of true.

The section Know the Limitations of memset() When Initializing shows a particular example of how bool can get an unexpected value.

What to remember:
Avoid comparing Booleans to true.
Prefer comparing to false (== false, != false) or comparing like this: if(boolVar), if( ! boolVar).

How to automate catching this:
(To some extent) V676. It is incorrect to compare the variable of BOOL type with TRUE (+RU).
See also the How to Automate Catching the memset() Problems section.

How to force the bugs caused by this to show themselves:
In progress... Help is welcome.
See also the How to Force the memset() Problems to Show Themselves section.

The memset() Function Is a Warning Sign

This section describes the problems with the memset() function.

Know the Limitations of memset() When Initializing

The problem can show itself in the code like this:

typedef enum
{
    ID_A,
    ..
    ID_D,
    ID_IDLE  // Let's say this value is 4.
}
ENUM_TYPE;

ENUM_TYPE myEnumArray[2]; // Array of enumerations.
bool myBoolArray[3];      // Array of Booleans.

// Initialize all the items of `myEnumArray` to `ID_IDLE`:
memset(myEnumArray, ID_IDLE, sizeof(myEnumArray));

// Initialize all the items of `myBoolArray` to `true`:
memset(myBoolArray, true, sizeof(myBoolArray));

Recall that memset() converts its second parameter to unsigned char and copies that value to the bytes starting with the address pointed to by the first parameter.

Converting to unsigned char can be problematic if the value of ID_IDLE does not fit in the unsigned char. With years the code can grow such that the ID_IDLE becomes 0x100. After converting to unsigned char it becomes 0x00 and all the items of myEnumArray instead of being intialized to ID_IDLE start being initialized to ID_A.

There is one more problem with memset().
If in the code above the sizeof(ENUM_TYPE) is 1 and sizeof(bool) is 1 then we are fine. However the sizeof(ENUM_TYPE) is not guaranteed. The sizeof(bool) is not guaranteed either. That is why if the compiler decides to use 2 bytes for each of those types above then
the items of myEnumArray will be initialized not to the value of ID_IDLE (0x04) but to the value of 0x0404,
the items of myBoolArray will be initialized not to the value of true (0x01) but to the value of 0x0101.
For such values (0x0101) of myBoolArray
if your code compares those with false (== false, != false) or evaluates like this: if(myBoolArray[0]), if( ! myBoolArray[0]) then the code will be behaving as expected,
but if your code compares those with true (== true, != true) then the comparison is likely to fail. That is one of the reasons why I recommend to avoid comparing Booleans to true.

What to remember:

If you need to initialize the multi-byte varaibles (or the variables whose size is not guaranteed), or arrays of those, or structures containig those, etc.
to the value not containing 0 in all the bit positions,
and not containing 1 in all the bit positions,
then applying memset() for such an initialization is a risk.

In other words you can safely use memset() to initialize
1-byte-long variables (or types containing those)
or multi-byte variables to 0 in all bit positions
or multi-byte variables to 1 in all bit positions.
In all other cases applying memset() is unsafe.

Andrey Karpov's Experience With memset()

In his posts Andrey has described many more problems with memset() function. The number and severity of the problems really impresses. Please see his posts:

See also:
CWE-14: Compiler Removal of Code to Clear Buffers

How to Automate Catching the memset() Problems

In progress...

PVS-Studio:
The following diagnostics catch some of the memset() problems.

How to Force the memset() Problems to Show Themselves

In progress...
The problem described in section Know the Limitations of memset() When Initializing is likely to cause different behavior

  • at different optimization levels of the same compiler (run the same tests with no optimization, highest optimization for speed, highest optimization for size),
  • on architectures with different native size (especially 8-bit vs. 16-bit).

Visual Studio 2017 Enterprise x64 (19.16.27027.1 for x86) (cl.exe /EHsc <file.cpp>):
std::cout << "myEnumArray[0]: 0x" << std::hex << myEnumArray[0] << "\n";
myEnumArray[0]: 0x4040404.

IAR C/C++ Compiler V6.70.2.6274/W32 for ARM (arm\bin\iccarm.exe):
--enum_is_int Force the size of all enumeration types to be at least 4 bytes.

Comparing Floating Point Numbers For [In]Equality

Here is what one needs to know before comparing the floating point numbers for [in]equality.

Overload Resolution

2018.??.?? [C++TCG] David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor. C++ Templates: The Complete Guide (2nd Edition), Appendix C: Overload Resolution, paper pages 681 - 696.

const T vs. T const (aka const West vs. East const)

Story, Grounds, and Mechanism:
1999.02.?? [ctvtc] Dan Saks. const T vs. T const. Embedded Systems Programming, FEBRUARY 1999.
1998.06.?? [pcd] Dan Saks. Placing const in Declarations. Embedded Systems Programming, JUNE 1998.

Where It Can Matter:
2018.??.?? [C++TCG] David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor. C++ Templates: The Complete Guide (2nd Edition), section Some Remarks About Programming Style, paper pages xxxi - xxxii.
2011.09.26 [scs] Dan Saks. Simplifying const Syntax. Dr.Dobb's, September 26, 2011.

Videos:
(About East const vs. const West, mostly for fun)

Know the Danger of printf() and Similar Functions

How hackers exploit printf():
2012.02.01 [wnuw] PVS Articles: Andrey Karpov. Wade not in unknown waters. Part two.

Nested (Local) Functions

Walter Bright (the creator of the D programming language) told during November 2017 meeting of http://nwcpp.org/ how the nested (local) functions (that do not exist in C and C++) can help to avoid using goto. If I remember it right the approach looks like this:

// Not a valid C or C++ code:
bool enclosingFunc(..)  // A function that contains a nested function.
{
  char *c = NULL;       // Some resource pointers/descriptors/handles of the enclosingFunc().
  FILE *fileDescriptor = NULL;
  
  void nestedFunc(..)   // The nested function, is used to deallocate 
                        // the resources of the enclosingFunc().
  {                     // Has access to the enclosingFunc()'s local variables (c, fileDescriptor).
                        // Those resources are "global" for the nestedFunc().
    if(fileDescriptor != NULL)      // Deallocates the resources.
    {
      .. fclose(fileDescriptor)..
    }
    if(c != NULL)
    {
      delete[] c;
    }
  };
  
  // The regular code of the enclosingFunc():
  ..
  c = new char [..];  // Allocates a resource.
  ..
  if(..)
  {
    fileDescriptor = fopen(..);   // Allocates another resource.
    ..
        if(<someFailure>)
        {
          nestedFunc();  // Instead of `goto` deallocates the resources using the nested function,
          return false;  // returns in-place.
        }
        ..
            if(<someOtherFailure>)
            {
              nestedFunc(); // Instead of `goto` deallocates the resources using the nested function,
              return false; // returns in-place.
            }
  }
  
  return true;
}

The effect of the nested (local) functions can be simulated in C++ by using the following approaches.

1. The static function of the local class.

bool enclosingFunc(..)
{ ..
  class localClass
  {
    static void nestedFunc(..) { .. }
  };
  
  // The regular code of the enclosingFunc().
}

Example:
[MC++D], 11.6.2 The Logarithmic Dispatcher and Casts, p.281.
Features:
Very simple however the localClass::nestedFunc() has no access to the enclosingFunc()'s local variables (but the pointers/references to those can be passed to nestedFunc() as the arguments).

2. [MExcC++], Item 33: Simulating Nested Functions.

See Also
(GNU C Extension) 6.4 Nested Functions.

Distinguish Between Size and Length

Sometimes I see the code similar to this:

#include <unistd.h>  // Declares `read()`.

char buffer[6]; // The buffer to read the chars to.
int bytesRead;  // The number of chars that have been read.
if((bytesRead = read(.., buffer, 5)) > 0)  // Read up to 5 bytes to the buffer (instead of `5` 
    // there can be `sizeof(buffer) - sizeof((char)'\0')` but that's not the point).
{   // The `bytesRead` contains the number of bytes actually read (1..5).
    buffer[bytesRead] = '\0';  // Null-terminate the sequence of chars in the buffer.

(Here is the read() man page)

At some point in the future we can make a change like this:

< char buffer[6];
---
> wchar_t buffer[6];

(we replace char with wchar_t)
Here, for simplicity, I assume that
wchar_t is 2 bytes in size (but in reality its size is implementation-defined, see below),
whereas char is 1 byte in size,
see

  • C++11 Late Working Paper n3242, 5.3.3 Sizeof, item 1, fragments
    • "sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1",
    • "in particular, sizeof(..), .. , and sizeof(wchar_t) are implementation-defined".
  • C11 (N1570) 6.5.3.4 The sizeof and _Alignof operators, item 4, fragment "When sizeof is applied to an operand that has type char, unsigned char, or signed char, (or a qualified version thereof) the result is 1").

After such a change we get problems:

  • the call read(.., buffer, 5) (or read(.., buffer, sizeof(buffer) - sizeof((char)'\0'))) requests the odd number of bytes, this can partially (incompletely) update one of the wchar_ts in the buffer (if the read() reads all 5 bytes (of the 5 requested) then the first 4 bytes will update the buffer[0] and buffer[1], and the 5th byte will update the half of the buffer[2]);
  • The call bytesRead = read(..) updates the bytesRead variable with the number of bytes (not the number of characters). But the subsequent fragment buffer[bytesRead] = '\0' requires the index which (for our wchar_t) should be twice less than the number of bytes. E.g. if the bytesRead = read(..) reads 4 bytes (of the 5 requested) and thus updates the buffer[0] and buffer[1] then we need to null-terminate the buffer[2] but the line buffer[bytesRead] = '\0' will null-terminate buffer[4], and the buffer[3] will stay UNinitialized.

Based on similar observations I strictly distinguish between the size and length.

  • I use the concept of size to designate the size in bytes only (typically it is a result of the sizeof() operator), and I always prefer writing "size (in bytes)" rather than just "size".
  • To designate the number of elements in a container/array, number of (char/wchar_t) characters in a string, I use the concept of length.
  • The length is always less than or equal to the size (in bytes).
  • The concept of index (e.g. array index) originates from the concept of length (but not from the concept of size).

E.g. for the declaration wchar_t buffer[6]

  • the buffer length is 6, the index originates from length and has a range from 0 to (length - 1) (from 0 to 5);
  • the buffer size (in bytes) is at least 12 (and includes the optional alignment padding between (and probably before and after) the array elements).

The functions read()/write() take as the last argument (and they return) the number of bytes - a concept originating from size (not from the length). If we want to use index as the last argument to read()/write() then the index needs to be multiplied by the size of the element (index * sizeof(buffer[0])) and if we want to use the value returned by read()/write() to index the buffer then the value needs to be divided by the size of the element (bytesRead / sizeof(buffer[0])).

#include <unistd.h>  // Declares `read()`.

char /* or wchar_t */ buffer[6]; // The buffer to read the chars to.
int bytesRead;  // The number of bytes that have been read.
if((bytesRead = read(.., buffer,
                     sizeof(buffer) - sizeof(buffer[0]))) 
   > 0)
    // Read to the buffer up to 5 (char or wchar_t) characters
    // (up to 5 bytes for `char` or up to (>=10) bytes for `wchar_t`).
{   // The `bytesRead` contains the number of bytes actually read
    // (1..5 bytes for `char`, 1..(>=10) bytes for `wchar_t`).
    
    size_t index = bytesRead / sizeof(buffer[0]); // Calculate the index (to null-terminate).
    
    // Make sure the bytesRead is even for wchar_t case:
    // ASSERT((index * sizeof(buffer[0])) == bytesRead);
    
    buffer[index] = '\0';  // Null-terminate the sequence of
                           // (char or wchar_t) characters in the buffer.

The Clone() member function (or Virtual Copy Constructor)

  • [MEC++], Item 25: Virtualizing constructors and non-member functions.
  • [MExcC++], Item 31: Smart Pointer Members, Part 2: Toward a ValuePtr
    • p.188 top, item (c);
    • p.193, first paragraph of section "Adding Extensibility Using Traits".

Know About the Compiler's Resource Allocation and Deallocation Order

Conclusion
For the consistency with the compiler-generated code deinitialize the data in the reverse order of initialization.

Rationale
Let's say we have a class D derived from the base classes B1 and B2. The D class also has member variables m1 and m2 of some classes C1 and C2, and a default consructor:

class D : public B1, public B2
{
  C1 m1;
  C2 m2;
public:
  D();
};

Let's say we define the constructor like this:

D::D() :
  // Member Initialization List starts here:
  B2(),
  B1(),
  m2(),
  m1()
  // End of Member Initialization List.
{}

By default the g++ 4.6 (C++98/03) compiler will keep silent about the inconsistency between
the order of calls in the programmer-written Member Initialization List and
the code the compiler actually generates.

The order of calls in the actual compiler-generated code will be like this:

D::D() :
  // Member Initialization List starts here:
  B1(),
  B2(),
  m1(),
  m2(),
  // End of Member Initialization List.
{}

To get a warning/error about such an inconsistency one can use the compiler flags -Wreorder/-Werror=reorder.

The compiler orders the calls in the Member Initialization List (in all the constructors) to strictly correspond to the class definition (see the first-most listing in this topic) in order to guarantee the strict REVERSE order of DEinitialization in the destructor.

Confirmation in C++98:

12.6.2 Initializing bases and members
5: Initialization shall proceed in the following order:
— First, and only for the constructor of the most derived class as described below, virtual base classes shall be initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base class names in the derived class base-specifier-list.
— Then, direct base classes shall be initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, nonstatic data members shall be initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
— Finally, the body of the constructor is executed.
[Note: the declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. ]

Also:

12.6 Initialization
3: When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order; see 8.3.4. [Note: destructors for the array elements are called in reverse order of their construction. ]

12.4 Destructors
6: A destructor for class X calls the destructors for X’s direct members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. .. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). .. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

5.3.5 Delete
6: The delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

See Also:

  • Search for "reverse order" in the C++ Standard.
  • g++ man page (or here and here): -Wreorder/-Werror=reorder.

System Calls Failing with EINTR

POSIX/Linux-specific.

A number of system calls upon failure return -1 (or a negative value) and specify the reason of failure by setting the errno to some value.

The errno value of EINTR is a special case, it is not a failure as such, it can happen when everything is correct.
E.g. our thread launches a child process (and continues the execution), then our thread calls select() with a 10-second time-out (and gets suspended), after 3 seconds the child process terminates (returns from its main()) which causes the SIGCHLD signal to be sent to our thread, a signal handler is executed by the OS (within the context of our thread), the signal handler does nothing special (nothing related to select()), then the signal handler returns, and the suspended select() returns -1 to our thread, with errno equal to EINTR.
To summarize, our thread has called select() with a 10-second time-out, but the call has returned after 3 seconds; in general case this means that our thread should call select() again but this time with a 7-second time-out (and repeat the attempts as long as the EINTR happens, until the time-out is exhausted).

Another example of when a system call can fail with the EINTR is when running a Linux console application in a terminal and re-sizing (or minimizing) the terminal window.

In general case I would handle the system call failures something like this:

do
{
    int retVal = select(..);   // The system call.
    int errNum = errno;        // Read `errno` immediately after the system call.

    if(retVal == -1)
    {
        switch(errNum) 
        {
            case EINTR:
                .. // Adjust the time-out.
                continue; // Skip to the end of the loop body and repeat the iteration.
            case ENOMEM:
                WARN(..);
                .. // Memory allocation Failure Handling.
                return NULL; 
            case EBADF:
            case EINVAL:
                ASSERT(.."Bug in our code! errno: %d (%s)." .. errNum, strerror(errNum) ..);
                return NULL; // If NDEBUG is defined (Release Version) then the ASSERT() above is ignored
                             // and the execution reaches this line.
            default:
                ASSERT(.."Unknown or Unhandled Failure! "
                       "Bug in our code or system call API has changed. errno: %d (%s)." 
                       .. errNum, strerror(errNum) ..);
                return NULL;
        }
    }
    else if(retVal >= 0)
        break;  // Exit from the `while()` loop.
    else // (retVal <= (-2))
    {
        ASSERT(.."System call has returned an undocumented value, "
               "Bug in system call or API has changed. retVal: %d, errno: %d (%s)." 
               .. retVal, errNum, strerror(errNum) ..);
        return NULL;
    }
}   
while(1);

Info:

Variable Length Arrays are C99 Feature, But Not C++

This item is applicable to (strictly) Standard C++ only.

Variable Length Arrays are the arrays whose length (number of elements) is a run-time value (as opposed to compile-time constant). E.g.

void f(size_t runTimeValue)
{
  int stackBasedArray[runTimeValue];  // Variable Length Array.

By default the g++ (4.6) does not complain. But if we get pedantic (-Wextra -pedantic) then we get

  • [ARM gcc 4.6.4 (linux) #1] warning: ISO C++ forbids variable length array .. [-Wvla]
  • [x86-64 clang 5.0.0 #1] warning: variable length arrays are a C99 feature [-Wvla-extension]

Thus strictly speaking the Variable Length Arrays are not a C++ feature as of C++17 (but are C99 feature). The (strictly) Standard C++ code should not use them.

Reasons:
One of the reasons why the Variable Length Arrays are not a C++ feature is the fact that C++ in general does not encourage the C-style arrays. Some other reasons are connected with uses in C++ different than in C, e.g. the arrays inside of classes, arrays of class instances, references to arrays, etc. See more details in the "See Also" section.

Some more reasons are listed in [ynovlas] Why doesn't C++ support variable length arrays (VLAs)?

Future:

  • C++: There is an ongoing discussion about the alternatives for the Variable Length Arrays. See
  • C:
    • [dsa]: Now, keep in mind that VLAs are effectively “on the way out” of the C language. Although they have been present in the standard since C99, in C11 they have been demoted to an “optional” feature. Many compiler implementations never implemented VLAs, and many never will. C++ doesn’t support VLAs, and those compilers that do provide them do so as a non-standard extension. So, if you’re interested in writing highly portable C code, or code that will likely be migrated to C++, VLAs should be off the table, even if your compiler just happens to support them.
    • [ANSI_C11], [C11_N1570]:
      • 6.7.6.2 Array declarators. 4. Variable length arrays are a conditional feature that implementations need not support.
      • Foreword. 6. Major changes from the previous edition include:
        — conditional (optional) features (including some that were previously mandatory)
        .
      • 6.10.8.3 Conditional feature macros. 1. __STDC_NO_VLA__ The integer constant 1, intended to indicate that the implementation does not support variable length arrays or variably modified types.

See Also:

Know the Danger of alloca()

The alloca() function allocates the space on the stack by adjusting the stack pointer and returns the pointer to the allocated block. The allocation is released upon function return but not when the pointer to such an allocation goes out of scope. Thus if the alloca() is called in the loop then each loop iteration allocates more and more space on the stack which can easily cause the stack overflow.

Such a behavior is fundamentally differnt from the behavior of its counterparts (the ones that also allocate on the stack) -
the ordinary arrays (whose number of elements is specified with a compile-time constant),
and the variable length arrays (whose number of elements is specified with a run-time value).
Both of these couterparts reuse the space on the stack during each iteration of the loop.

The other problem with alloca() is that it is non-standard. The variable length arrays seem to be a better alternative since they are standard in at least [C99] and conditional (optional) in [C11].

Demonstration in C:

#include <stdio.h>
#include <alloca.h>

void F(void)
{
  // Do the loop having the `alloca()`:
  printf("alloca():\n");
  {
    int i = 0;
    do
    {
      char a = 'a';
      void *p = alloca(0xF0);
      int b = 1;

      printf("&i == %p; &a == %p; p == %p; &b == %p\n", &i, &a, p, &b);
    }
    while(++i < 3);
  }

  // Try to reuse the stack:
  printf("alloca() repeated:\n");
  {
    int i = 0;
    do
    {
      char a = 'a';
      void *p = alloca(0xF0);
      int b = 1;

      printf("&i == %p; &a == %p; p == %p; &b == %p\n", &i, &a, p, &b);
    }
    while(++i < 3);
  }
}

int main(void)
{
  F();
  return 0;
}
# Save as `alloca.c`.
make alloca
# cc     alloca.c   -o alloca
./alloca 
# alloca():
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2680; &b == 0x7ffc5c2a2794
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2580; &b == 0x7ffc5c2a2794
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2480; &b == 0x7ffc5c2a2794
# alloca() repeated:
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2380; &b == 0x7ffc5c2a2794
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2280; &b == 0x7ffc5c2a2794
# &i == 0x7ffc5c2a2790; &a == 0x7ffc5c2a278f; p == 0x7ffc5c2a2180; &b == 0x7ffc5c2a2794
cc --version
# cc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609

The demonstration shows that all the variables reuse the space on the stack (stay at the same addresses) in different iterations and in different loops, but the allocation made with alloca() and pointed to by the p pointer changes (consumes more and more space on the stack).

The alloca() function is described in Linux man pages (e.g. here, here) and MSDN page for alloca() (which referes to _alloca()).

Thanks to Andrey Karpov for explaining and demonstrating this.

More Info:

  • PVS-Studio Diagnostics V505. The 'alloca' function is used inside the loop. This can quickly overflow stack.
  • gcc: -Walloca, -Walloca-larger-than=n (This option also warns when "alloca" is used in a loop), -fstack-protector (Emit extra code to check for buffer overflows, such as stack smashing attacks), -mwarn-dynamicstack (Emit a warning if the function calls "alloca" ..), search for alloca.

Know the Special Member Functions

Know All the Effects of the Empty Destructor

Some people were taught to always write the destructor just in case, even if it does nothing (is empty). I aslo heard that some compilers and/or static analyzers were complaining if a class had no destructor.

Starting in C++11 the explicit destructor

  • disables the (implicit, by-compiler) generation of the move operations,
  • makes the generation of the copy operations deprecated (i.e. disabled in some future version of C++).

See the end (Things to Remember section) of [EMC++] Item 17: Understand special member function generation, fragments:

  • Move operations are generated only for classes lacking explicitly declared move operations, copy operations, and a destructor.
  • Generation of the copy operations in classes with an explicitly declared destructor is deprecated.

Broader Picture:

  • Know the Special Member Functions.
  • When the empty destructor may be needed:
    • If the destructor needs to be virtual or pure virtual. See
    • Pimpl-Idiom-specific, [MExcC++]:
      • Item 30: Smart Pointer Members, Part 1: A Problem with auto_ptr, section "What About auto_ptr Members?", last but one paragraph on paper page 185: "If you don't want to provide the definition of Y, you must write the X2 destructor explicitly, even if it's empty".
      • Item 31: Smart Pointer Members, Part 2: Toward a ValuePtr, section "A Simple ValuePtr: Strict Ownership Only", first regular paragraph: "Either the full definition of Y must accompany X, or the X destructor must be explicitly provided, even if it's empty".

There Should Be a Strong Reason for Writing the Destructor

If you use Smart Pointers and other Smart Resource Releasers (see below) then you don't need the explicit (i.e. manually written) destructor, the implicit (i.e. the compiler-generated) one will do the right thing in the right order.
See also Rule of zero.

The explicit destructor is a warning sign.

Broader Picture:

Know What Happens If Rule of Three Is Violated

Recall what the Rule of Three is. Let's try to violate it in C++98.
Let's imagine that we develop a class that handles a resource - the heap-allocated block - by using a raw pointer. For simplicity it will be our own implementation of string:

#include <cstring>  // strlen(), strcpy().
class String
{
    char *p;  // Raw pointer to a heap-allocated block.

It has a constructor that allocates a resource:

public:
    String(const char *st) :
        p( (st == NULL)                // If param is NULL 
           ? NULL                      // then initialize the member variable to NULL
           : new char[strlen(st) + 1]) // otherwise allocate the block, initialize the p to point to that block,
    {                                  // (we don't consider the possible exceptions here)
        if(st != NULL)
        {
            strcpy(p, st);             // and initialize the block.
        }
    }

It has the destructor that deallocates the resource:

    ~String()
    {
        if(p != NULL)
        {
            delete[] p;
            p = NULL;
        }
    }

And it has an accessor to the resource data:

    const char *c_str() const 
    {
        return p;
    }
}; // End of the class.

To summarize, the user provides the destructor, but does not provide the copy constructor and the copy assignment operator, thus violating the rule of three.

Now let's imagine that there is a function that prints the contents of our class:

#include <iostream>  // std::cout, std::endl.
void printStr(String param)  // Takes parameter by value.
{
    std::cout << param.c_str() << std::endl;
}

In general it can be any function that takes the parameter (param) by value (rather than by reference or by pointer). And we call such a function:

int main()
{
    String s("abc");
    // . . .
    printStr(s);  // Argument `s` is passed by value.
    // . . .
    return 0;
}

Let's consider the call to printStr(s).
The compiler-generated code allocates the space for the parameter param of the function void printStr(String param). In particular case the space can be allocated in a CPU register, but in general case and in the case where the param does not fit in the CPU register, the space for param is allocated on the stack. Then the space needs to be initialized. It should be initialized with the call to the copy constructor String::String(const String& other). But the user has not provided this constructor. Since the copy constructor is a special member function, the compiler generates one for the user. Here is what the compiler-generated copy constructor looks like:

String::String(// Hidden from the programmer, parameter `String *this`.
               const String& other) :
    p(other.p)
{
}

It has two parameters.
The first one is hidden form the programmer, it is named this and has type String * (the book [C++TCG] states that the name of this parameter is *this and type is String&, but for simplicity we will not consider that). This parameter points to the param.
The second parameter other is a reference to the argument s.
The fragment p(other.p) copies the address from s.p to the param.p, i.e. it does the shallow copying of the pointer. Thus during the execution of the function printStr() the two pointers point to the same heap-allocated block: the param.p and the s.p.
Upon return form the function printStr() the parameter param gets destroyed, for this the destructor is called for param. The destructor deallocates the heap-allocated block (to which the s.p still points) and the s.p (after return from printStr()) becomes a dangling pointer. Any subsequent access to the data pointed to by s.p (including the destruction of the variable s in which case the destructor of s will try to deallocate the block again) will cause the undefined behavior. Reading the data pointed to by the dangling pointer can return the unpredictable values because that part of the heap can be already allocated for a differnt block (of the same or different thread). Writing to the location pointed to by the dangling pointer s.p can corrupt some other block's data (of the same or other thread) or even can corrupt the heap's internal data structures.

Similar things happen with the copy assignment operator. If the programmer does not provide one (for a class managing a resource and having the destructor and/or copy constructor) then the compiler-generated copy assignment operator will create a shallow copy, and again, different pointers (in different instances) will be pointing to the same heap-allocated block. Destruction of one instance will result in a dangling pointer in the other instance.

Broader Picture:

Polymorphic Behavior of Non-Virtual Destructor

2016.03.23 [crto] Jacek Galowicz. Const References to Temporary Objects.
2000.12.01 [gcwywescf] Andrei Alexandrescu and Petru Marginean. "Generic: Change the Way You Write Exception-Safe Code — Forever". Search for "how to achieve polymorphic behavior of the destructor" on page 2 of 3.

Know the Peculiarities of Writing the Assignment Operator

When writing the Copy-Assignment Operator the following logic needs to be applied.

  • Know the Special Member Functions (in particular [EC++3] Item 5: Know what functions C++ silently writes and calls, [EMC++] Item 17: Understand special member function generation) and try to avoid writing the explicit assignment operator.
  • If you have to write then try to avoid the Copy-And-Swap Idiom (e.g. if your Assignment Operator does not make throwing calls) since the idiom creates a copy thus lowering down the performance.
  • Use the Copy-And-Swap Idiom as the last resort.
  • Don't forget to call the parent class' Assignment Operator, see [EC++3] Item 12: Copy all parts of an object.

TODO: Review for the Move-Assignment Operator.

Broader Picture:

Inlining

I often see the code like this:

// Header "B.h":
class B
{
// non-private:
    void b(); // Declaration of a member function.
};    
// File "B.cpp":
void B::b() // Definition of the member function.
{ 
    /* Very short or empty implementation. */ 
}

Since the member function B::b() is very short (or empty) it can be more efficient to inline that function instead of calling it. However if the function is declared and defined separately as shown above then the compiler, while compiling the caller, does not know the body of the function B::b(). For example, we have class D derived from class B shown above.

// Header "D.h":
#include "B.h"
class D : public B
{
    void d();
};
// File "D.cpp":
#include "D.h"
void D::d()
{
    b(); // Calls the inherited `B::b()`.
}

During compilation of "D.cpp" the compiler runs the file through the preprocessor (which expands all the #include directives) and gets the translation unit that looks about like this:

class B
{
    void b();
};    
class D : public B
{
    void d();
};
void D::d()
{
    b();
}

When the compiler generates the code for D::d() the compiler does not see the body of function B::b() called from D::d() and cannot make a decision about inlining B::b() into the D::d(). That decision is either not made at all (and inlining is not done) or is postponed until linking. In the latter case the linker knows the body of B::b() and has enough information to make a decision about inlining. And that will be the linker who will be able to make the inlining (I call such an inlinig the link-time inlining).
We know that inlining is not guaranteed. But if inlining is done then it is likely that the compile-time inlining will be more efficient than the link-time inlining. In order to encourage the compile-time inlining we should move the definition of B::b() to the declaration site:

// Header "B.h":
class B
{
    // Now both declaration and definition of `B::b()`:
    void b() { /* Very short or empty implementation. */ }
};

and to remove the separate definion of B::b() from the "B.cpp":

1,4d0
< void B::b() // Definition of the member function.
< {
<     /* Very short or empty implementation. */
< }

Remember: The short member functions are good candidates for compile-time inlining. It is likely more efficient to place their definition together with the declaration.

More about inlining:
[EC++3]:

  • Item 30: Understand the ins and outs of inlining.
  • Item 2: Prefer consts, enums, and inlines to #defines.
  • Item 5: Know what functions C++ silently writes and calls, fragment: "All these functions will be both public and inline".
  • Item 44: Factor parameter-independent code out of templates, p. 214(paper)/235(file), Fragment: "(Provided, of course, you refrain from declaring that function inline. If it’s inlined, each instantiation of SquareMatrix::invert will get a copy of SquareMatrixBase::invert’s code (see Item 30), and you’ll find yourself back in the land of object code replication.)".
  • Also search for inline, inlining in the e-book.

[MEC++]:

  • Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI; p.118(paper)/135(file), middle, paragraph starting with: "The real runtime cost of virtual functions has to do with their interaction with inlining. For all practical purposes, virtual functions aren’t inlined.".
  • TODO: Other inline, inlining occurrences in Item 26, and other places.
  • Also search for inline, inlining in the e-book.

TODO: [ExcC++].
To be continued.

Know the Danger of Overflowing (and Underflowing) the Signed Types

The signed and unsigned integer types behave differently when overflown or underflown.

First let's consider by the example of the unsigned types.
If we have an unsigned 8-bit integer (whose range is from 0 (0x00) to 255 (0xFF)) with value 0, and we decrement it (thus causing the underflow), then we get the value 255 (0xFF). Fully defined value and fully defined behavior. In the same way incrementing the value 255 (0xFF) (thus causing the overflow) will result in the value 0 (0x00). Also a fully defined value and fully defined behavior.

Confirmation in C++98:

3.9.1 Fundamental types
4: Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2^n where n is the number of bits in the value representation of that particular size of integer. Footnote 41)
41) This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type.

But for the signed integers the picture is different.
If we have a signed 8-bit integer, e.g. int8_t (with the range
from std::numeric_limits<int8_t>::min() in C++, or INT8_MIN in C,
to std::numeric_limits<int8_t>::max() in C++, or INT8_MAX in C),
with the lowest value std::numeric_limits<int8_t>::min() (INT8_MIN) and we decrement such a value then we can get the Undefined Behavior! (Here and below, for brevity and simplicity, the integer promotion rules are ignored, i.e. it is assumed that the 8-bit value is not extended to 16-bit or 32-bit value then decremented without the overflow and then the least significant byte is saved back to the 8-bit integer, all in fully defined way. We assume that any signed integer type (int8_t, int16_t, etc.) and/or all of those types can be implemented using int (or using a type larger than int) on some implementations)
The same is for incrementing the highest value std::numeric_limits<int8_t>::max() (INT8_MAX).
It is emphasized, not just the value is undefined, but the whole behavior is undefined.

The same is for the floating point types.

C++98:

5 Expressions
5: If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed.

For the portable code this all means the following:

All the calculations that rely on the integer type overflowing, e.g. the checksums (CRC), hash codes, ring buffer indices, must use the unsigned types (this is also applicable to all the unsigned concepts such as size in bytes, length of the sequence (e.g. the number of elements in an array), array/container index, offset, pointer arithmetic, bitmasks (and shifts for them)).

Negating the signed value (-signedValue) and

  • assigning such a value to oneself (x = -x;) or to a variable of the same type (y = -x;)
  • or passing such a value as an argument (corresponding to the parameter of the same type), e.g.
    void foo(int y); main() { int x =..; foo(-x); }

can cause overflow, i.e. Undefined Behavior, if before the negation the value was std::numeric_limits<int??_t>::min() (INT??_MIN). This is because negating such a value in some representations (see below) can result in a value (std::numeric_limits<int??_t>::max() + 1) (INT??_MIN + 1) which does not fit in the type.

Reasons:

One of the reasons why unsigend and signed integers behave so differently is the fact that the signed integers have at least 3 implementation-dependent representations in C.

Future:

The Proposals to C and C++ named "Signed Integers are Two’s Complement" tell that there is an intention to use in C and C++ only one representation for the signed integers (which allows applying the same algorithms and/or hardware logic both for signed and unsigned integer arithmetic (at least addition and subtraction, and very likely multiplication and division)). After those Proposals end up in the C and C++ Standards the overflow and underflow of the signed integers are expected to become fully defined (the same way as for the unsigned integers).
The same fully defined effect for signed integers in gcc/g++ can be achieved now by using the -fwrapv command line argument (but the code relying on such a wrapping behavior is considered non-portable as of C11 and C++17).

More Info:

  • (Expected at/after CppCon 2018) Talk/Video: JF Bastien, "Signed integers are two's complement".
  • C++Now 2018 Closing Keynote: Undefined Behavior and Compiler Optimizations, John Regehr (abstract, slides, video).
  • C++Now 2014 Undefined Behavior in C++: What is it, and why do you care? Marshall Clow (video).
  • gcc/g++ command line arguments: -fstrict-overflow, -Wstrict-overflow[=n], -Wno-overflow, -fwrapv, -fsanitize=signed-integer-overflow, -fsanitize=float-cast-overflow, -ftrapv, also search for "overflow".
  • 2018.08.20 [woioingi] Davin McCall. Wrap on integer overflow is not a good idea.

Info Sources About the Exceptions

Partially based on Deb Haldar's Top 15 C++ Exception handling mistakes and how to avoid them [t15c++ehm], Where do we go from here? section.

Books and Articles at a High Level

Q&As

Videos

Internals and ABIs

  • 2011.01.10 [.eh_f] Airs – Ian Lance Taylor. ".eh_frame".

Certain Code Fragments Should Not Throw Exceptions

In general that's the code that is executed from the moment when the exception is thrown and until entering the catch-handler (i.e. during the stack unwinding process, see [e&su]). Some of that code is implicitly generated by the compiler (hiddenly from the programmer).
The programmer-written code fragments are

  • destructors, see
    Meyers' [EC++3], Item 8: Prevent exceptions from leaving destructors,
    Sutter's [ExcC++], Item8: Writing Exception-Safe Code - Part1.
  • operator delete, operator delete[], see
    Haldar's [t15c++ehm], Mistake # 5: Throwing exceptions in destructors or in overloaded delete or delete[] operator.
  • constructors of the exception classes, see
    Haldar's [t15c++ehm], Mistake # 12: Throwing exception in an exception class constructor.

C++ Exception Specifications

The Herb Sutter's article "A Pragmatic Look at Exception Specifications" ([pl@es]) makes think that the exception specifications

  • instead of providing the compile-time nothrow-guarantee (throw()) or throws-only-these-ones-guarantee (throw(A, B))
  • provide the run-time checks/asserts (which are pessimization, i.e. they lower down the performance).

Another confirmation is in [eses].

The Meyers' [EMC++], Item 14: Declare functions noexcept if they won’t emit exceptions,
in the beginning makes one think that noexcept helps to optimize in certain cases (by means of replacing the copy operations with the move operations). But in the end still shows that there are no any copile-time guarantees (a noexcept-function can call the functions without the exception specification, and the compilers (so far) don't complain) but there are run-time checks ("your program will be terminated if an exception tries to leave the function").
See also [t15c++ehm], Mistake # 9: Not realizing the implications of "noexcept" specification.


See also:

  • [t15c++ehm] Deb Haldar. Top 15 C++ Exception handling mistakes and how to avoid them
  • [MEC++], Item 14: Use exception specifications judiciously.

C++ Exceptions: TODO.

  • Concise high-level explanation of what the C++ exceptions are for, is it possible to live without the exceptions (with the exceptions completely disabled at compile time)? If yes then what is the cost?
    (Member vars that are arrays are inited by calling the default Ctor for each array element, if those Ctors acquire the resources then that acquisition can fail, how to return such a failure from the default Ctor?
    How and to who to return the failure from a Ctor of a global var? What happens if the global var Ctor throws?)
  • In which cases is it impossible to use the exceptions? Bare metal? (no OS to terminate the code that does not handle the exc)
  • Some articles state that today's compilers generate such a code that the exceptions have zero-cost in successful case (i.e. the exception is not thrown), but Walter Bright (creator of D) said (at http://nwcpp.org/ November 2017 meeting) that zero-cost exceptions are a myth. Who is right? What particularly stands behind (what code is generated upon) try, catch(T), catch(...), throw, thrown-case, not-thrown-case, etc.? At C++Now2018's "How Compilers Reason About Exceptions" session it has been recommended to read the ABI docs and the [.eh_f] Airs – Ian Lance Taylor. ".eh_frame" (still TODO).
  • The Niall Douglas' talks about the exceptions, see the presentation links recommended by Phil Nash during "Option(al) Is Not a Failure" (TODO).

Curious Fragments and Questions

Abstract Class Constructors: public? private?

By default the constructors of an abstract class should be protected.

The abstract class (i.e. the one having at least one pure virtual function) is not suitable for instantiation but for inheriting from it only. I.e. its constructors do not need to be public, right? Are there any cases when they do?

Are there any cases (other than disabling the copy functions, etc.) when it makes sense to make such constructors private?


This page contains contributions from: