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
- Identifiers
- Booleans
- The
memset()
Function Is a Warning Sign - Comparing Floating Point Numbers For [In]Equality
- Overload Resolution
- const T vs. T const (aka
const West
vs.East const
) - Know the Danger of
printf()
and Similar Functions - Nested (Local) Functions
- Distinguish Between Size and Length
- The
Clone()
member function (or Virtual Copy Constructor) - Know About the Compiler's Resource Allocation and Deallocation Order
- System Calls Failing with
EINTR
- Variable Length Arrays are C99 Feature, But Not C++
- Know the Danger of
alloca()
- Know the Special Member Functions
- Inlining
- Know the Danger of Overflowing (and Underflowing) the Signed Types
- Info Sources About the Exceptions
- Curious Fragments and Questions
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 typechar
,unsigned char
, orsigned char
, (or a qualified version thereof) the result is 1.
6.2.5 Types, paragraph 15:
The three types
char
,signed char
, andunsigned char
are collectively called the character types. The implementation shall definechar
to have the same range, representation, and behavior as eithersigned char
orunsigned char
.45)
45)
CHAR_MIN
, defined in<limits.h>
, will have one of the values 0 orSCHAR_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 typeshort int
SHRT_MAX +32767 // 2^15 − 1
— maximum value for an object of type unsignedshort int
USHRT_MAX 65535 // 2^16 − 1
— minimum value for an object of typeint
INT_MIN -32767 // −(2^15 − 1)
— maximum value for an object of typeint
INT_MAX +32767 // 2^15 − 1
— maximum value for an object of typeunsigned int
UINT_MAX 65535 // 2^16 − 1
— minimum value for an object of typelong int
LONG_MIN -2147483647 // −(2^31 − 1)
— maximum value for an object of typelong int
LONG_MAX +2147483647 // 2^31 − 1
— maximum value for an object of typeunsigned long int
ULONG_MAX 4294967295 // 2^32 − 1
— minimum value for an object of typelong long int
LLONG_MIN -9223372036854775807 // −(2^63 − 1)
— maximum value for an object of typelong long int
LLONG_MAX +9223372036854775807 // 2^63 − 1
— maximum value for an object of typeunsigned 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 rangeINT_MIN
toINT_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 oflong int
, which shall be greater than the rank ofint
, which shall be greater than the rank ofshort int
, which shall be greater than the rank ofsigned 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
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.
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 asBOOL
orBool
or other Boolean implementations cause questions (e.g. "What is the difference of this type frombool
?") and typically force to take a look at the implementation in order to understand the difference frombool
and limitations originating from that. - When converting a number of types to
bool
the Boolean conversions guarantee that thebool
will take one of the two values - eitherfalse
ortrue
, whereas converting to other implementations can result in more than two values, e.g. converting value5
to some implementation, let’s say namedBOOL
, can result in a value not equal toFALSE
and not equal toTRUE
. 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 toFALSE
and not equal toTRUE
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 tobool
is consistent (any non-null pointer is converted totrue
) 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 tofalse
(i.e.
converting the non-null pointer0xAB00
results in the equivalent offalse
(0x00
), but
converting the non-null pointer0xAB01
results in the equivalent oftrue
(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);
- V721. The VARIANT_BOOL type is utilized incorrectly. The true value (VARIANT_TRUE) is defined as -1 (+RU).
For Booleans prefer using the type bool
and its values false
and true
.
Help is wanted...
- V724. Converting integers or pointers to BOOL can lead to a loss of high-order bits. Non-zero value can become 'FALSE' (+RU).
Help is wanted...
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.
This section describes the problems with the memset()
function.
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.
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
In progress...
PVS-Studio:
The following diagnostics catch some of the memset()
problems.
- V575 The 'memset' function processes value XXXX (+RU)
Catches the poblem of narrowing conversion of the second argument tounsigned char
(when the value of0x100
becomes0x00
). - V601 The 'true' value is implicitly cast to the integer type (+RU)
Catches the poblem of implicit cast of thebool
second argument tounsigned char
. - V597 The compiler could delete the 'memset'... (+RU)
Catches the security problem when the compiler could delete thememset()
function call. - The diagnostic catching the problem
when the items ofmyEnumArray
get initialized not to the value ofID_IDLE
(0x04
) but to the value of0x0404
has been added to the PVS-Studio's To Do list (on 2019.03.0x). We are looking forward to the new diagnostic.
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
.
Here is what one needs to know before comparing the floating point numbers for [in]equality.
- GCC's
-Wfloat-equal
option on Options to Request or Suppress Warnings page or gcc man page. - [cfpn] Comparing Floating Point Numbers, 2012 Edition by brucedawson.
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.
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)
East const
: C++Now 2018: Jon Kalb "This is Why We Can't Have Nice Things" (5:29).const West
: C++Now 2018: Jonathan Müller "A Fool's Consistency" (4:21).
How hackers exploit printf()
:
2012.02.01
[wnuw] PVS Articles: Andrey Karpov. Wade not in unknown waters. Part two.
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.
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)
andsizeof(unsigned char)
are 1", - "in particular,
sizeof(..)
, .. , andsizeof(wchar_t)
are implementation-defined".
- "
- C11 (N1570) 6.5.3.4 The
sizeof
and_Alignof
operators, item 4, fragment "Whensizeof
is applied to an operand that has typechar
,unsigned char
, orsigned char
, (or a qualified version thereof) the result is 1").
After such a change we get problems:
- the call
read(.., buffer, 5)
(orread(.., buffer, sizeof(buffer) - sizeof((char)'\0'))
) requests the odd number of bytes, this can partially (incompletely) update one of thewchar_t
s in thebuffer
(if theread()
reads all 5 bytes (of the 5 requested) then the first 4 bytes will update thebuffer[0]
andbuffer[1]
, and the 5th byte will update the half of thebuffer[2]
); - The call
bytesRead = read(..)
updates thebytesRead
variable with the number of bytes (not the number of characters). But the subsequent fragmentbuffer[bytesRead] = '\0'
requires the index which (for ourwchar_t
) should be twice less than the number of bytes. E.g. if thebytesRead = read(..)
reads 4 bytes (of the 5 requested) and thus updates thebuffer[0]
andbuffer[1]
then we need to null-terminate thebuffer[2]
but the linebuffer[bytesRead] = '\0'
will null-terminatebuffer[4]
, and thebuffer[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 from0
to(length - 1)
(from0
to5
); - 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.
- [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".
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
.
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:
- Primitives Interrupted by Signals (this link has been courteously provided by Ivan Ponomarev).
errno
Man Page.select()
Man Page.- Alphabetic list of all Linux man pages.
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:
- [C99], search for "variable length".
- [ANSI_C11], [C11_N1570], search for "variable length", "conditional feature".
- [dsa] How does dynamic stack allocation work in C, specifically regarding variable-length arrays?
- [ynovlas] Why doesn't C++ support variable length arrays (VLAs)?
- gcc:
-Wvla
,-Wvla-larger-than=n
. - (GNU C Extension for C90 and C++) 6.20 Arrays of Variable Length.
- [P0785R0] Runtime-sized arrays and a C++ wrapper.
- [n3810] Alternatives for Array Extensions.
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 foralloca
.
- C++98/03: [EC++3] Chapter 2: Constructors, Destructors, and Assignment Operators.
- C++11: What the Move Semantics Are. Part 1 [ms1], Part 2 [ms2].
- C++11: [EMC++] Item 17: Understand special member function generation.
- The rule of three/five/zero.
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
- [EC++3] Item 7: Declare destructors virtual in polymorphic base classes;
- Rule of zero.
- Pimpl-Idiom-specific, [MExcC++]:
- Item 30: Smart Pointer Members, Part 1: A Problem with
auto_ptr
, section "What Aboutauto_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 SimpleValuePtr
: 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".
- Item 30: Smart Pointer Members, Part 1: A Problem with
- If the destructor needs to be virtual or pure virtual. See
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 the Special Member Functions.
- Smart Pointers:
- C++98/03: [EC++3]
- Chapter 3: Resource Management
- Item 29: Strive for exception-safe code
- Item 45: Use member function templates to accept "all compatible types."
- Search for "smart" in the e-book
- C++98/03: [MEC++] Item 28: Smart pointers (and other items referred to in Item 28).
- C++11/14: [EMC++] Chapter 4. Smart Pointers
- Boost.SmartPtr: The Smart Pointer Library.
- C++98/03: [EC++3]
- Smart Resource Releasers: C++98/03: [gcwywescf].
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:
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.
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:
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.
- 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.
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.
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
(Ongoing )
C++ Exception FAQ on isocpp.org (still TODO).1996.??.??
More Effective C++ – 35 new ways to improve your programs and designs – items 9 through 15 [MEC++].1999.11.18
Exceptional C++ – 47 Engineering puzzles, programming problems and solutions – items 8 through 19 [ExcC++].2000.12.01
[gcwywescf] Andrei Alexandrescu and Petru Marginean. "Generic: Change the Way You Write Exception-Safe Code — Forever". Dr. Dobb's, December 01, 2000.2001.12.17
[MExcC++], Chapter "Exception Safety Issues and Techniques", Items 17 - 23.2004.10.??
C++ Coding Standards – 101 Rules, Guidelines and Best Practices – items 68 through 75 [C++CS].2015.??.??
[e&su] MSDN. Exceptions and Stack Unwinding in C++.2016.08.03
[t15c++ehm] Deb Haldar. Top 15 C++ Exception handling mistakes and how to avoid them.
Q&As
- The Copy-And-Swap Idiom.
Videos
2018.05.09
C++Now 2018: Michael Spencer, "How Compilers Reason About Exceptions" (to go online in 2018.06).2018.05.09
C++Now 2018: Phil Nash, "Option(al) Is Not a Failure" (to go online in 2018.06). Presentation links.
Internals and ABIs
2011.01.10
[.eh_f] Airs – Ian Lance Taylor. ".eh_frame".
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.
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.
- 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).
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:
- Jakub Wilk (@jwilk), Andrey Karpov, Ivan Ponomarev.