Team LiB
Previous Section Next Section

18.7. Inherently Nonportable Features

18.7. 固有的不可移植的特征

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.

编写可以容易从一个机器移到其他机器的低级程序是 C 程序设计语言的一个特点。将程序移到新机器的过程称为“移植”,所以说 C 程序是可移植的

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 语言定义了一些固有不可移植的特征。算术类型的大小随机器不同而变化的事实(第 2.1 节),就是我们已经遇到过的一个这样的不可移植特征。本节将讨论 C++ 的另外两个从 C 语言继承来的不可移植特征:位域和 volatile 限定符。这些特征可使与硬件接口的直接通信更容易。

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.

C++ 还增加了另一个不可移植特征(从 C 语言继承来的):链接指示,它使得可以链接到用其他语言编写的程序。

18.7.1. Bit-fields

18.7.1. 位域

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:

位域必须是整型数据类型,可以是 signedunsigned。通过在成员名后面接一个冒号以及指定位数的常量表达式,指出成员是一个位域:

     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.

mode 位域有两个位,modified 只有一位,其他每个成员有三个位。(如果可能)将类定义体中按相邻次序定义的位域压缩在同一整数的相邻位,从而提供存储压缩。例如,在前面的声明中,5 个位域将存储在一个首先与位域 mode 关联的 unsigned int 中。位是否压缩到整数以及如何压缩与机器有关。

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.

通常最好将位域设为 unsigned 类型。存储在 signed 类型中的位域的行为由实现定义。



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:

用与类的其他数据成员相同的方式访问位域。例如,作为类的 private 成员的位域只能从成员函数的定义和类的友元访问:

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

通常使用内置按位操作符(第 5.3 节)操纵超过一位的位域:

     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:

定义了位域成员的类通常也定义一组内联成员函数来测试和设置位域的值。例如,File 类可以定义成员 isReadisWrite

     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.

有了这些成员函数,现在就可以将位域声明为 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

18.7.2. volatile 限定符

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.

volatile 的确切含义与机器相关,只能通过阅读编译器文档来理解。使用 volatile 的程序在移到新的机器或编译器时通常必须改变。



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.

直接处理硬件的程序常具有这样的数据成员,它们的值由程序本身直接控制之外的过程所控制。例如,程序可以包含由系统时钟更新的变量。当可以用编译器的控制或检测之外的方式改变对象值的时候,应该将对象声明为 volatile。关键字 volatile 是给编译器的指示,指出对这样的对象不应该执行优化。

The volatile qualifier is used in much the same way as is the const qualifier. It is an additional modifier to a type:

用与 const 限定符相同的方式使用 volatile 限定符。volatile 限定符是一个对类型的附加修饰符:

     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.

display_registerint 类型的 volatile 对象;curr_taskvolatile 对象的指针;ixa 是整数的 volatile 数组,该数组的每个元素都认为是 volatile 的;bitmap_bufvolatile Screen 对象,它的每个成员都认为是 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.

用与定义 const 成员函数相同的方式,类也可以将成员函数定义为 volatilevolatile 对象只能调用 volatile 成员函数。

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:

第 4.2.5 节介绍了 const 限定符与指针的相互作用,volatile 限定符与指针之间也存在同样的相互作用。可以声明 volatile 指针、指向 volatile 对象的指针,以及指向 volatile 对象的 volatile 指针:

     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.

像用 const 一样,只能将 volatile 对象的地址赋给指向 volatile 的指针,或者将指向 volatile 类型的指针复制给指向 volatile 的指针。只有当引用为 volatile 时,我们才可以使用 volatile 对象对引用进行初始化。

Synthesized Copy Control Does Not Apply to Volatile Objects
合成的复制控制不适用于 volatile 对象

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.

对待 constvolatile 的一个重要区别是,不能使用合成的复制和赋值操作符从 volatile 对象进行初始化或赋值。合成的复制控制成员接受 const 形参,这些形参是对类类型的 const 引用,但是,不能将 volatile 对象传递给普通引用或 const 引用。

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:

如果类希望允许复制 volatile 对象,或者,类希望允许从 volatile 操作数或对 volatile 操作数进行赋值,它必须定义自己的复制构造函数和/或赋值操作符版本:

     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.

通过将复制控制成员的形参定义为 const volatile 引用,我们可以从任何各类的 Foo 对象进行复制或赋值:普通 Foo 对象、const Foo 对象、volatile Foo 对象或 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.

虽然可以定义复制控制成员来处理 volatile 对象,但更深入的问题是复制 volatile 对象是否有意义,对该问题的回答与任意特定程序中使用 volatile 的原因密切相关。



18.7.3. Linkage Directives: extern "C"

18.7.3. 链接指示 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.

C++ 程序有时需要调用用其他程序设计语言编写的函数,最常见的一语言是 C 语言。像任何名字一样,必须声明用其他语言编写的函数的名字,该声明必须指定返回类型和形参表。编译器按处理普通 C++ 函数一样的方式检查对外部语言函数的调用,但是,编译器一般必须产生不同的代码来调用用其他语言编写的函数。C++ 使用链接指示指出任意非 C++ 函数所用的语言。

Declaring a Non-C++ Function
声明非 C++ 函数

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

作为例子,看看头文件 cstdlib 中声明的一些 C 函数。该头文件中声明形如:

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

第一种形式由关键字 extern 后接字符串字面值,再接“普通”函数声明构成。字符串字面值指出编写函数所用的语言。

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

可以将多重声明形式应用于整个头文件。例如,C++ 的 cstring 头文件可以像这样:

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

当将 #include 指示在复合链接指示的花括号中的时候,假定头文件中的所有普通函数声明都是用链接指示的语言编写的函数。链接指示可以嵌套,所以,如果头文件包含了带链接指示的函数,该函数的链接不受影响。

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

允许将 C++ 从 C 函数库继承而来的函数定义为 C 函数,但不是必须定义为 C 函数——决定是用 C 还是用 C++ 实现 C 函数库,是每个 C++ 实现的事情。



Exporting Our C++ Functions to Other Langauges
导出 C++ 函数到其他语言

By using the linkage directive on a function definition, we can make a C++ function available to a program written in another language:

通过对函数定义使用链接指示,使得用其他语言编写的程序可以使用 C++ 函数:

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

要求编译器支持对 C 语言的链接指示。编译器可以为其他语言提供链接说明。例如,extern "Ada"extern "FORTRAN" 等。

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.

支持什么语言随编译器而变。你必须查阅用户指南,获得关于编译器可以提供的任意非 C 链接说明的进一步信息。



Preprocessor Support for Linking to C

对链接到 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++.

有时需要在 C 和 C++ 中编译同一源文件。当编译 C++ 时,自动定义预处理器名字 __cplusplus(两个下划线),所以,可以根据是否正在编译 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++.

链接指示与函数重载之间的相互作用依赖于目标语言。如果语言支持重载函数,则为该语言实现链接指示的编译器很可能也支持 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:

C++ 保证支持的唯一语言是 C。C 语言不支持函数重载,所以,不应该对下面的情况感到惊讶:在一组重载函数中只能为一个 C 函数指定链接指示。用带给定名字的 C 链接声明多于一个函数是错误的:

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

在 C++ 程序中,重载 C 函数很常见,但是,重载集合中的其他函数必须都是 C++ 函数:

     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.

可以从 C 程序和 C++ 程序调用 calc 的 C 版本。其余函数是带类型形参的 C++ 函数,只能从 C++ 程序调用。声明的次序不重要。

Pointers to extern "C" Functions
extern "C" 函数和指针

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.

使用 pf 调用函数的时候,假定该调用是一个 C 函数调用而编译该函数。

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

C 函数的指针与 C++ 函数的指针具有不同的类型,不能将 C 函数的指针初始化或赋值为 C++ 函数的指针(反之亦然)。



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.

一些 C++ 编译器可以接受前面的赋值作为语言扩展,尽管严格说来它是非法的。



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.

这个声明是说,f1 是一个不返回值的 C 函数,它有一个形参,该形参是不返回值并接受单个形参的函数的指针。链接指示应用于该函数指针以及 f1。调用的时候,必须将 C 函数名字或 C 函数指针传递给它。

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:

因为链接指示应用于一个声明中的所有函数,所以必须使用类型别名,以便将 C 函数的指针传递给 C++ 函数:

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


Team LiB
Previous Section Next Section