Team LiB
Previous Section Next Section

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.

 

Image Note

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.

 

Image 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

 

Image 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.

 

Image Note

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.

 

Image Note

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

 

Image 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.

 

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.

 

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);

 

 
Team LiB
Previous Section Next Section