位域,链接指示

位域

  • 位域,类/结构体的数据成员,用于容纳一定的位数.

  • 位域的定义: 整数类型 变量名: 位数.如:

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 语言为例来解释链接指示的用法.

使用 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 *);/* 同上 */
    }
}

生成 C 语言使用的函数

  • 在函数定义时,若使用 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





















































你可能感兴趣的:(位域,链接指示)