19.8. Inherently Nonportable Features
To support low-level programming, C++ defines some features that are inherently nonportable. A nonportable feature is one that is machine specific. Programs that use nonportable features often require reprogramming when they are moved from one machine to another. The fact that the sizes of the arithmetic types vary across machines (§ 2.1.1, p. 32) is one such nonportable feature that we have already used.
In this section we’ll cover two additional nonportable features that C++ inherits from C: bit-fields and the volatile
qualifier. We’ll also cover linkage directives, which is a nonportable feature that C++ adds to those that it inherits from C.
19.8.1. Bit-fields
A class can define a (nonstatic
) data member as a bit-field. A bit-field holds a specified number of bits. Bit-fields are normally used when a program needs to pass binary data to another program or to a hardware device.
INFO
The memory layout of a bit-field is machine dependent.
A bit-field must have integral or enumeration type (§ 19.3, p. 832). Ordinarily, we use an unsigned
type to hold a bit-field, because the behavior of a signed
bit-field is implementation defined. We indicate that a member is a bit-field by following the member name with a colon and a constant expression specifying the number of bits:
typedef unsigned int Bit;
class File {
Bit mode: 2; // mode has 2 bits
Bit modified: 1; // modified has 1 bit
Bit prot_owner: 3; // prot_owner has 3 bits
Bit prot_group: 3; // prot_group has 3 bits
Bit prot_world: 3; // prot_world has 3 bits
// operations and data members of File
public:
// file modes specified as octal literals; see § 2.1.3 (p. 38)
enum modes { READ = 01, WRITE = 02, EXECUTE = 03 };
File &open(modes);
void close();
void write();
bool isRead() const;
void setWrite();
};
The mode
bit-field has two bits, modified
only one, and the other members each have three bits. Bit-fields defined in consecutive order within the class body are, if possible, packed within adjacent bits of the same integer, thereby providing for storage compaction. For example, in the preceding declaration, the five bit-fields will (probably) be stored in a single unsigned int
. Whether and how the bits are packed into the integer is machine dependent.
The address-of operator (&
) cannot be applied to a bit-field, so there can be no pointers referring to class bit-fields.
WARNING
Ordinarily it is best to make a bit-field an unsigned
type. The behavior of bit-fields stored in a signed
type is implementation defined.
Using Bit-fields
A bit-field is accessed in much the same way as the other data members of a class:
void File::write()
{
modified = 1;
// . . .
}
void File::close()
{
if (modified)
// . . . save contents
}
Bit-fields with more than one bit are usually manipulated using the built-in bitwise operators (§ 4.8, p. 152):
File &File::open(File::modes m)
{
mode |= READ; // set the READ bit by default
// other processing
if (m & WRITE) // if opening READ and WRITE
// processing to open the file in read/write mode
return *this;
}
Classes that define bit-field members also usually define a set of inline member functions to test and set the value of the bit-field:
inline bool File::isRead() const { return mode & READ; }
inline void File::setWrite() { mode |= WRITE; }
19.8.2. volatile
Qualifier
WARNING
The precise meaning of volatile
is inherently machine dependent and can be understood only by reading the compiler documentation. Programs that use volatile
usually must be changed when they are moved to new machines or compilers.
Programs that deal directly with hardware often have data elements whose value is controlled by processes outside the direct control of the program itself. For example, a program might contain a variable updated by the system clock. An object should be declared volatile
when its value might be changed in ways outside the control or detection of the program. The volatile
keyword is a directive to the compiler that it should not perform optimizations on such objects.
The volatile
qualifier is used in much the same way as the const
qualifier. It is an additional modifier to a type:
volatile int display_register; // int value that might change
volatile Task *curr_task; // curr_task points to a volatile object
volatile int iax[max_size]; // each element in iax is volatile
volatile Screen bitmapBuf; // each member of bitmapBuf is volatile
There is no interaction between the const
and volatile
type qualifiers. A type can be both const
and volatile
, in which case it has the properties of both.
In the same way that a class may define const
member functions, it can also define member functions as volatile
. Only volatile
member functions may be called on volatile
objects.
§ 2.4.2 (p. 62) described the interactions between the const
qualifier and pointers. The same interactions exist between the volatile
qualifier and pointers. We can declare pointers that are volatile
, pointers to volatile
objects, and pointers that are volatile
that point to volatile
objects:
volatile int v; // v is a volatile int
int *volatile vip; // vip is a volatile pointer to int
volatile int *ivp; // ivp is a pointer to volatile int
// vivp is a volatile pointer to volatile int
volatile int *volatile vivp;
int *ip = &v; // error: must use a pointer to volatile
*ivp = &v; // ok: ivp is a pointer to volatile
vivp = &v; // ok: vivp is a volatile pointer to volatile
As with const
, we may assign the address of a volatile
object (or copy a pointer to a volatile
type) only to a pointer to volatile
. We may use a volatile
object to initialize a reference only if the reference is volatile
.
Synthesized Copy Does Not Apply to volatile
Objects
One important difference between the treatment of const
and volatile
is that the synthesized copy/move and assignment operators cannot be used to initialize or assign from a volatile
object. The synthesized members take parameters that are references to (nonvolatile
) const
, and we cannot bind a nonvolatile
reference to a volatile
object.
If a class wants to allow volatile
objects to be copied, moved, or assigned, it must define its own versions of the copy or move operation. As one example, we might write the parameters as const volatile
references, in which case we can copy or assign from any kind of Foo
:
class Foo {
public:
Foo(const volatile Foo&); // copy from a volatile object
// assign from a volatile object to a nonvolatile object
Foo& operator=(volatile const Foo&);
// assign from a volatile object to a volatile object
Foo& operator=(volatile const Foo&) volatile;
// remainder of class Foo
};
Although we can define copy and assignment for volatile
objects, a deeper question is whether it makes any sense to copy a volatile
object. The answer to that question depends intimately on the reason for using volatile
in any particular program.
19.8.3. Linkage Directives: extern "C"
C++ programs sometimes need to call functions written in another programming language. Most often, that other language is C. Like any name, the name of a function written in another language must be declared. As with any function, that declaration must specify the return type and parameter list. The compiler checks calls to functions written in another language in the same way that it handles ordinary C++ functions. However, the compiler typically must generate different code to call functions written in other languages. C++ uses linkage directives to indicate the language used for any non-C++ function.
INFO
Mixing C++ with code written in any other language, including C, requires access to a compiler for that language that is compatible with your C++ compiler.
Declaring a Non-C++ Function
A linkage directive can have one of two forms: single or compound. Linkage directives may not appear inside a class or function definition. The same linkage directive must appear on every declaration of a function.
As an example, the following declarations shows how some of the C functions in the cstring
header might be declared:
// illustrative linkage directives that might appear in the C++ header <cstring>
// single-statement linkage directive
extern "C" size_t strlen(const char *);
// compound-statement linkage directive
extern "C" {
int strcmp(const char*, const char*);
char *strcat(char*, const char*);
}
The first form of a linkage directive consists of the extern
keyword followed by a string literal, followed by an “ordinary” function declaration.
The string literal indicates the language in which the function is written. A compiler is required to support linkage directives for C. A compiler may provide linkage specifications for other languages, for example, extern "Ada", extern "FORTRAN"
, and so on.
Linkage Directives and Headers
We can give the same linkage to several functions at once by enclosing their declarations inside curly braces following the linkage directive. These braces serve to group the declarations to which the linkage directive applies. The braces are otherwise ignored, and the names of functions declared within the braces are visible as if the functions were declared outside the braces.
The multiple-declaration form can be applied to an entire header file. For example, the C++ cstring
header might look like
// compound-statement linkage directive
extern "C" {
#include <string.h> // C functions that manipulate C-style strings
}
When a #include
directive is enclosed in the braces of a compound-linkage directive, all ordinary function declarations in the header file are assumed to be functions written in the language of the linkage directive. Linkage directives can be nested, so if a header contains a function with its own linkage directive, the linkage of that function is unaffected.
INFO
The functions that C++ inherits from the C library are permitted to be defined as C functions but are not required to be C functions—it’s up to each C++ implementation to decide whether to implement the C library functions in C or C++.
Pointers to extern "C"
Functions
The language in which a function is written is part of its type. Hence, every declaration of a function defined with a linkage directive must use the same linkage directive. Moreover, pointers to functions written in other languages must be declared with the same linkage directive as the function itself:
// pf points to a C function that returns void and takes an int
extern "C" void (*pf)(int);
When pf
is used to call a function, the function call is compiled assuming that the call is to a C function.
A pointer to a C function does not have the same type as a pointer to a C++ function. A pointer to a C function cannot be initialized or be assigned to point to a C++ function (and vice versa). As with any other type mismatch, it is an error to try to assign two pointers with different linkage directives:
void (*pf1)(int); // points to a C++ function
extern "C" void (*pf2)(int); // points to a C function
pf1 = pf2; // error: pf1 and pf2 have different types
WARNING
Some C++ compilers may accept the preceding assignment as a language extension, even though, strictly speaking, it is illegal.
Linkage Directives Apply to the Entire Declaration
When we use a linkage directive, it applies to the function and any function pointers used as the return type or as a parameter type:
// f1 is a C function; its parameter is a pointer to a C function
extern "C" void f1(void(*)(int));
This declaration says that f1
is a C function that doesn’t return a value. It has one parameter, which is a pointer to a function that returns nothing and takes a single int
parameter. The linkage directive applies to the function pointer as well as to f1
. When we call f1
, we must pass it the name of a C function or a pointer to a C function.
Because a linkage directive applies to all the functions in a declaration, we must use a type alias (§ 2.5.1, p. 67) if we wish to pass a pointer to a C function to a C++ function:
// FC is a pointer to a C function
extern "C" typedef void FC(int);
// f2 is a C++ function with a parameter that is a pointer to a C function
void f2(FC *);
Exporting Our C++ Functions to Other Languages
By using the linkage directive on a function definition, we can make a C++ function available to a program written in another language:
// the calc function can be called from C programs
extern "C" double calc(double dparm) { /* ... */ }
When the compiler generates code for this function, it will generate code appropriate to the indicated language.
It is worth noting that the parameter and return types in functions that are shared across languages are often constrained. For example, we can almost surely not write a function that passes objects of a (nontrivial) C++ class to a C program. The C program won’t know about the constructors, destructors, or other class-specific operations.
INFO
Preprocessor Support for Linking to C
To allow the same source file to be compiled under either C or C++, the preprocessor defines _ _cplusplus
(two underscores) when we compile C++. Using this variable, we can conditionally include code when we are compiling C++:
#ifdef __cplusplus
// ok: we're compiling C++
extern "C"
#endif
int strcmp(const char*, const char*);
Overloaded Functions and Linkage Directives
The interaction between linkage directives and function overloading depends on the target language. If the language supports overloaded functions, then it is likely that a compiler that implements linkage directives for that language would also support overloading of these functions from C++.
The C language does not support function overloading, so it should not be a surprise that a C linkage directive can be specified for only one function in a set of overloaded functions:
// error: two extern "C" functions with the same name
extern "C" void print(const char*);
extern "C" void print(int);
If one function among a set of overloaded functions is a C function, the other functions must all be C++ functions:
class SmallInt { /* . . . */ };
class BigNum { /* . . . */ };
// the C function can be called from C and C++ programs
// the C++ functions overload that function and are callable from C++
extern "C" double calc(double);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);
The C version of calc
can be called from C programs and from C++ programs. The additional functions are C++ functions with class parameters that can be called only from C++ programs. The order of the declarations is not significant.
INFO
Exercises Section 19.8.3
Exercise 19.26: Explain these declarations and indicate whether they are legal:
extern "C" int compute(int *, int);
extern "C" double compute(double *, double);