位域,类/结构体的数据成员,用于容纳一定的位数.
位域的定义: 整数类型 变量名: 位数.如:
typedef unsigned char Flags; class Stream{ Flags _Error: 1;/* _Error 就是一个位域,在内存中占有 1 bit.所以其可取值:0/1 */ Flags _Eof: 1; public: Flags _Mode: 2;/* _Mode 在内存中占有2bit,可取值:0,1,2,3 */ };
整数类型可以是 unsigned,或者 signed,建议为 unsigned 类型,signed 类型并没有什么意义.
位域的使用: 与类或者结构体的其他数据成员一样.
取址运算符不能应用于位域
位域不可以是类的静态数据成员.
不论是那种分配方式,位域与结构体类似,即先声明的成员位于低地址,后声明的成员位于高地址.
__attribute__((__gcc_struct__)) 默认的分配方式
此时将不区分类型合并所有的位域,并将原类型中最大尺寸作为基本存储单元.
基本存储单元: 是编译器在给位域分配内存空间时的基本单位,并且这些分配给位域的内存是以基本存储单元大小的整数倍递增的.
/* 此时基本存储单元为: unsigned short,并且 sizeof(unsigned short)*8 > 3+7. * 所以此时只需要一个基本存储单元的大小即可.即 sizeof(Test)==sizeof(unsigned short); * 并且此时 [0,3) 位存放着 a,[8,15) 位存放着 b. */ struct Test{ unsigned short a: 3; unsigned char b: 7; }__attribute__((__gcc_struct__)) ; /* 由于 sizeof(unsigned short)*8 < 3+14;所以需要 2 个基本存储单元. * 即 sizeof(Test)==2*sizeof(unsigned short). * 此时 b 在下一个可存储下它的存储单元中分配内存,即: [0,3) 用于存放 a,[16,30) 用于存放 b. */ struct Test{ unsigned short a: 3; unsigned char b: 14; } ;
__attribute__((__packed__)) 最少存储空间策略,此时合并所有位域,对变量使用单字节方式对齐.
/* 此时位域所需位数 3+7=10 bit,所以将分配 2 个字节(16 bit)存放 a,b. * 并且 [0,3) 存放着 a,[3,10) 存放着 b,即 a,b 紧邻. */ struct Test{ unsigned short a: 3; unsigned char b: 7; } __attribute__((__packed__)) ;
链接指示,用于使用其他程序设计语言(比如: C 语言)编写的函数.或者生成其他语言可用的函数(函数本身由 C++ 实现).下面以 C 语言为例来解释链接指示的用法.
extern "C" size_t strlen(const char *); extern "C"{ int strcmp(const char *,const char *); char* strcat(char *,const char *); }
其中'{}'的作用仅是将应用链接指示的声明聚合起来.并不起到作用域的作用.即此时'{}'内的函数在'{}'外也是可见的.
extern "C" int max(int a,int b); int max(double a,double b); char* max(const char *left,const char *right); int main(int argc,char *argv[]){ int a=3,b=7; max(a,b); /* 调用的是 max(int,int) 使用 C 语言编写 */ double ad=3.0,bd=7.0; max(ad,bd); /* 调用的是 max(double,double),C++ 编写 */ max("Hello","World");/* 调用的是 max(const char *,const char *),C++ 编写 */ }
即重载集合中最多只能有一个 C 语言编写的函数,其他使用 C++ 编写.
链接指示允许嵌套,此时函数声明将使用据其最近的链接指示.
/* funcs.h 文件 */ extern "C"{ int strcmp(const char *,const char *); int strlen(const char *); } /* funcs.cc 文件 */ extern "C"{ #include "funcs.h" } /* 则对 funcs.cc 文件预编译后的结果为: */ extern "C"{ extern "C"{ int strcmp(const char *,const char *); /* 使用据其最近的链接指示,即 extern "C" */ int strlen(const char *);/* 同上 */ } }
在函数定义时,若使用 extern "C",则编译器为该函数产生代码时,他将生成适合于 C 语言的代码.这样就可以在 C 语言中调用该函数,如:
/* max.cc */ extern "C" int max(int a,int b){ return a>b?a:b; } /* main.c */ int main(int argc,char *argv[]){ max(3,7);/* 将调用 max.cc 中使用 C++ 语言编写的函数. */ }
一个模块若想调用一个函数,则在链接时,必须要在当前模块或其他模块中可以找到该函数的定义.
为什么不能直接声明使用 C 语言编写的函数,如:
/* main.cc */ int max(int a,int b); extern "C" int min(int a,int b); int main(int argc,char *argv[]){ max(3,7); min(3,7); } /* max.c */ int max(int a,int b){ return a>b?a:b; } /* min.c */ int min(int a,int b){ return a>b?b:a; } /* 调用 g++ -S mian.cc; gcc -S *.c 生成 main.s,max.s,min.s 文件 */ /* main.s */ main: pushq %rbp movq %rsp, %rbp subq $16, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $7, %esi movl $3, %edi call _Z3maxii /* 此时调用的是 _Z3maxii 函数,要求其他模块具有 _Z3maxii 函数的定义 */ movl $7, %esi movl $3, %edi call min /* 此时调用的是 min 函数. */ movl $0, %eax leave ret /* min.s */ min: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -8(%rbp), %eax cmpl %eax, -4(%rbp) cmovle -4(%rbp), %eax popq %rbp ret /* max.s */ max: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %eax cmpl %eax, -8(%rbp) cmovge -8(%rbp), %eax popq %rbp ret /* 据上,可以看出在其他模块下仅有 min 函数的定义,而没有 _Z3maxii 函数的定义, * 所以链接阶段会报错,提示未定义的引用. */
g++ 编译器在编译时,会对源文件中出现的所有函数名进行名称修饰,用于支持语言特性(比如函数重载...)
gcc 编译器则不会对函数名进行修饰,如:
/* max.cc */ int max(int a,int b){ return a>b?a:b; } /* g++ -x 'c++' -S max.c -o max.s */ _Z3maxii: // 函数名 max() 被修饰为 _Z3maxii; pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %eax cmpl -8(%rbp), %eax jle .L2 movl -4(%rbp), %eax jmp .L3 .L2: movl -8(%rbp), %eax .L3: popq %rbp ret /* gcc -S max.c -o cmax.s */ max: // 没有进行函数名修饰 pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %eax cmpl %eax, -8(%rbp) cmovge -8(%rbp), %eax //PS: gcc 使用了条件转送指令,而 g++ 没有使用.. popq %rbp ret
extern "C" 的作用: 禁止编译器对同名同形参表(即参数的个数,类型均相同)的函数名进行名称修饰如:
/* main.cc */ extern "C" int max(int a,int b);//则说明禁止对 max(int,int) 进行名称修饰. int max(int a,int b,int c); int main(int argc,char *argv[]){ max(3,7); /* 匹配 max(int,int),因为 max(int,int) 使用了 extern "C" 修饰. * 所以此时不会进行名称修饰.即不会 call _Z3maxii;而是 call max. */ max(3,7,37); /* 匹配 max(int,int,int),而且并没有使用 extern "C" 修饰. * 所以是 call _Z3maxiii */ } /* g++ -S main.cc,可以验证上述 */ main: pushq %rbp movq %rsp, %rbp subq $16, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $7, %esi movl $3, %edi call max movl $37, %edx movl $7, %esi movl $3, %edi call _Z3maxiii movl $0, %eax leave ret