C++ issues

Updated: April 19, 2023

There are some additional techiniques that you can use in C++ programs.

CheckedPtr template

The <rcheck/malloc.h> header file defines a CheckedPtr template that you can use in place of a raw pointer in C++ programs. In order to use this template, you must link your program with librcheck.

This template acts as a smart pointer; its initializers obtain complete information about the heap buffer on an assignment operation and initialize the current pointer position. Any attempt to dereference the pointer causes bounds-checking to be performed and prints a diagnostic error in response an attempt to dereference a value beyond the bounds of the buffer.

You can modify this template to suit the needs of the program. The bounds-checking performed by the checked pointer is restricted to checking the actual bounds of the heap buffer, rather than the program's requested size.

Clean C

For C programs it's possible to compile individual modules that obey certain rules with the C++ compiler to get the behavior of the CheckedPtr template. C modules obeying these rules are written to a dialect of ANSI C referred to as Clean C.

The Clean C dialect is the subset of ANSI C that's compatible with the C++ language. Writing Clean C requires imposing coding conventions to the C code that restrict use to features that are acceptable to a C++ compiler. This section provides a summary of some of the more pertinent points to be considered. It is a mostly complete but by no means exhaustive list of the rules that must be applied.

To use the C++ checked pointers, the module, including all header files it includes, must be compatible with the Clean C subset. All the system header files for QNX Neutrino satisfy this requirement.

The most obvious aspect to Clean C is that it must be strict ANSI C with respect to function prototypes and declarations. The use of K&R prototypes or definitions isn't allowed in Clean C. Similarly, you can't use default types for variable and function declarations.

Another important consideration for declarations is that you must provide forward declarations when referencing an incomplete structure or union. This frequently occurs for linked data structures such as trees or lists. In this case, the forward declaration must occur before any declaration of a pointer to the object in the same or another structure or union. For example, you could declare a list node as follows:

struct ListNode;
struct ListNode {
   struct ListNode *next;
   void *data;
};

Operations on void pointers are more restrictive in C++. In particular, implicit coercions from void pointers to other types aren't allowed, including both integer types and other pointer types. You must explicitly cast void pointers to other types.

The use of const should be consistent with C++ usage. In particular, pointers that are declared as const must always be used in a compatible fashion. You can't pass const pointers as non-const arguments to functions unless you typecast the const away.

C++ example

Here's how you could use checked pointers in the overrun example given earlier to determine the exact source of the error:

typedef CheckedPtr<int> intp_t;
...
intp_t foo, p;
int i;
int opt;
opt = 1;
mallopt(MALLOC_CKBOUNDS, opt);
foo = (int *)malloc(10*4);
opt = M_HANDLE_ABORT;
mallopt(MALLOC_WARN, opt);
for (p = foo, i = 12; i > 0; p++, i--)
    *p = 89; /* a fatal error is generated here */
opt = M_HANDLE_IGNORE;
mallopt(MALLOC_WARN, opt);
free(foo);

Because you're using the CheckedPtr template, you must link this program with librcheck.