Section 18.7. Inherently Nonportable Features


18.7. Inherently Nonportable Features

One of the hallmarks of the C programming language is the ability to write low-level programs that can be readily moved from one machine to another. The process of moving a program to a new machine is referred to as "porting," so C programs are said to be portable.

To support low-level programming, C defines some features that are inherently nonportable. The fact that the size of the arithmetic types vary across machines (Section 2.1, p. 34) is one such nonportable feature that we have already encountered. In this section we'll cover two additional nonportable features that C++ inherits from C: bit-fields and the volatile qualifier. These features make it easier to interface directly to hardware.

C++ adds another nonportable feature to those that it inherits from C: linkage directives, which make it possible to link to programs written in other languages.

18.7.1. Bit-fields

A special class data member, referred to as a bit-field, can be declared to hold a specified number of bits. Bit-fields are normally used when a program needs to pass binary data to another program or hardware device.

The layout in memory of a bit-field is machine-dependent.



A bit-field must be an integral data type. It can be either signed or unsigned. 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;          Bit modified: 1;          Bit prot_owner: 3;          Bit prot_group: 3;          Bit prot_world: 3;          // ...      }; 

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 be stored in the single unsigned int first associated with the bit-field mode. Whether and how the bits are packed into the integer is machine-dependent.

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 manner as the other data members of a class. For example, a bit-field that is a private member of its class can be accessed only from within the definitions of the member functions and friends of its 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 (Section 5.3, p. 154):

      enum { READ = 01, WRITE = 02 }; // File modes      int main() {          File myFile;          myFile.mode |= READ; // set the READ bit          if (myFile.mode & READ) // if the READ bit is on              cout << "myFile.mode READ is set\n";      } 

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. For example, the class File might define the members isRead and isWrite:

      inline int File::isRead() { return mode & READ; }      inline int File::isWrite() { return mode & WRITE; }      if (myFile.isRead()) /* ... */ 

With these member functions, the bit-fields can now be declared as private members of class File.

The address-of operator (&) cannot be applied to a bit-field, so there can be no pointers referring to class bit-fields. Nor can a bit-field be a static member of its class.

18.7.2. volatile Qualifier

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 either the control or detection of the compiler. 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 is the const qualifier. It is an additional modifier to a type:

      volatile int display_register;      volatile Task *curr_task;      volatile int ixa[max_size];      volatile Screen bitmap_buf; 

display_register is a volatile object of type int. curr_task is a pointer to a volatile Task object. ixa is a volatile array of integers. Each element of the array is considered to be volatile. bitmap_buf is a volatile Screen object. Each of its data members is considered to be volatile.

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.

Section 4.2.5 (p. 126) 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 pointer to volatile      *ivp = &v;    // ok: ivp is pointer to volatile      vivp = &v;    // ok: vivp is 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 Control Does Not Apply to Volatile Objects

One important difference between the treatment of const and volatile is that the synthesized copy and assignment operators cannot be used to initialize or assign from a volatile object. The synthesized copy-control members take parameters that are const references to the class type. However, a volatile object cannot be passed to a plain or const reference.

If a class wants to allow volatile objects to be copied or to allow assignment from or to a volatile operand, it must define its own versions of the copy constructor and/or assignment operator:

      class Foo {      public:          Foo(const volatile Foo&);    // copy from a volatile object          // assign from a volatile object to a non volatile objet          Foo& operator=(volatile const Foo&);          // assign from a volatile object to a volatile object          Foo& operator=(volatile const Foo&) volatile;          // remainder of class Foo      }; 

By defining the parameter to the copy-control members as a const volatile reference, we can copy or assign from any kind of Foo: a plain Foo, a const Foo, a volatile Foo, or a const volatile Foo.

Although we can define the copy-control members to handle 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.



18.7.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. That declaration must specify the return type and parameter list. The compiler checks calls to external-language functions 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.

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 linkage directive must appear on the first declaration of a function.

As an example, let's look at some of the C functions declared in the cstdlib header. Declarations in that header might look something like

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

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.

Linkage Directives and Header Files

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 the header contained a function with a linkage directive the linkage of that function is unaffected.

The functions that C++ inherits from the C library are permitted to be defined as C functions but are not required to be C functionsit's up to each C++ implementation to decide whether to implement the C library functions in C or C++.



Exporting Our C++ Functions to Other Langauges

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.

Every declaration of a function defined with a linkage directive must use the same linkage directive.



Languages Supported by Linkage Directives

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.

What languages are supported varies by compiler. You must consult the user's guide for further information on any non-C linkage specifications it may provide.



Preprocessor Support for Linking to C

It can be useful sometimes to compile the same source file in both C or C++. The preprocessor name __cplusplus (two underscores) is automatically defined when compiling C++, so we can conditionally include code based on whether 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 only language guaranteed to be supported by C++ is C. The C language does not support function overloading, so it should not be a surprise that a linkage directive can be specified only for one C function in a set of overloaded functions. It is an error to declare more than one function with C linakage with a given name:

      // error: two extern "C" functions in set of overloaded functions      extern "C" void print(const char*);      extern "C" void print(int); 

In C++ programs, it is fairly common to overload C functions. However, the other functions in the overload set 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.

Pointers to extern "C" Functions

The language in which a function is written is part of its type. To declare a pointer to a function written in another programming language, we must use a linkage directive:

      // pf points to a C function returning void taking 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).



When there is such a mismatch, a compile-time error message is issued:

      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 

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 point-ers 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 typedef to pass a pointer to a C function to a C++ function:

      // FC is a pointer to 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 *); 

Exercises Section 18.7.3

Exercise 18.34:

Explain these declarations and indicate whether they are legal:

      extern "C" int compute(int *, int);      extern "C" double compute(double *, double); 




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net