自学内容网 自学内容网

【C语言】难点汇总复习(二)

目录

一、结构体

1.结构体内存对齐

2. 位段

3.联合体

4.枚举

二、编译链接过程

三、预处理详解

1.define定义常量

2.define定义宏

3.宏和函数的区别

四、部分关键字

1.extern

2.volatile

3.static


一、结构体

1.结构体内存对齐

对齐规则:

  1. 结构体的第一个成员对齐到和结构体起始位置偏移量为0的地址处。
  2. 其他成员变量对齐到对齐数的整数倍地址处;对齐数就是编译器默认对齐数和该成员变量大小中的较小值,VS中默认对齐数为8,Linux中没有默认对齐数,即对齐数就是该成员本身的大小。
  3. 结构体总大小是最大对齐数的整数倍,最大对齐数就是结构体中每个成员变量中最大的对齐数。
  4. 如果嵌套了结构体的情况下,嵌套的结构体对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体中成员的对齐数)的整数倍。

为什么存在内存对齐?

  1. 平台原因(移植原因):不是所有硬件平台都支持访问任意地址上的数据的,某些硬件平台只能在某些地址处取某些类型的数据,否则抛出硬件异常。
  2. 性能原因:为了访问未被对齐的内存,处理器可能需要访问两次内存,而对齐后的进需要一次内存访问。
  3. 总的来说,结构体的内存对齐是拿空间换时间的做法。

结构体在对齐方式不合适的情况下,我们可以自己更改默认对齐数:

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
//输出的结果是什么?
    printf("%d\n", sizeof(struct S));
    return 0;
}

//6

2. 位段

位段的内存分配:

  1. 位段的成员可以是int / unsigned int / signed int / char 等类型。
  2. 位段的空间上是按照需要以4个字节或者1个字节的方式来开辟的。
  3. 位段涉及很多不确定因素,是不跨平台的,注重可移植性的程序应避免使用位段。
  4. 和结构体相比,位段能达到同样的效果,并且能很好的节省空间,但存在跨平台问题。

展示一个在VS2013环境的使用案例:

3.联合体

联合体最大的特点是所有成员共用同一块内存空间,所以联合体也叫共用体。

联合体大小的计算:

  • 联合体的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
union Un1
{
    char c[5];
    int i;
};
union Un2
{
    short c[7];
    int i;
};
int main()
{
    //下⾯输出的结果是什么?
    printf("%d\n", sizeof(union Un1));
    printf("%d\n", sizeof(union Un2));
    return 0;
}

//8,16

可以使用联合体来判断当前机器的大小段:

int check_sys()
{
union
{
    int i;
    char c;
}un;
    un.i = 1;
    return un.c;//返回1是⼩端,返回0是⼤端
}

4.枚举

我们可以使用#define定义常量,为什么要用枚举?

枚举的优点:

  1. 增加代码的可读性和可维护性
  2. 和#define相比有类型检查,更严谨,也能调试
  3. 使用方便,能一次性定义多个常量
  4. 枚举是遵循作用域规则的,枚举声明在函数内就只能在函数内使用
enum Color//颜⾊
{
    RED=2,
    GREEN=4,
    BLUE=8
};

二、编译链接过程

 

  • 预处理阶段:删除所有#define,展开所有宏定义,处理所有条件编译、#include,删除所有注释,添加行号和文件名标识方便后续生成调试信息
  • 编译:进行词法分析、语法分析、语义分析及优化,生成汇编代码
  • 汇编:将汇编代码转换生成机器可执行的指令
  • 链接: 地址和空间分配,符号决议和重定位等步骤

三、预处理详解

1.define定义常量

建议不要加上分号; 否则可能会很容易造成问题

#define MAX 1000;
#define MAX 1000

2.define定义宏

#define SQUARE( x ) x * x

如上示例,这个宏接收一个参数x,然后把SQUARE(5)替换为5*5

但这个宏存在一个问题,就是在如下代码执行时,会产生与预期不符的情况:

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

//替换为:a + 1 * a + 1 结果为11
//要解决只能改为:#define SQUARE(x) (x) * (x)

因此在实现带参的宏定义时,最好加上括号,不要嫌弃括号太多,这是保证正确的必备途径 

3.宏和函数的区别

//宏有时候能做到函数做不到的事情,例如:

#define MALLOC(num, type)\
(type )malloc(num sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 sizeof(int));

四、部分关键字

1.extern

extern关键字的作用:

extern关键字用于声明一个变量或者函数,指明它是在其他文件中定义的,提醒编译器去其他文件中去找其定义

2.volatile

volatile关键字的作用:

volatile关键字用于告诉编译器当前修饰的变量可能会被意外修改,因此编译器不能直接去寄存器里拿这个变量,而必须去内存中去那这个变量

3.static

static的几个用处:

1.修饰局部变量,正常的局部变量在出了作用域后就会销毁,使用static修饰后能使局部变量生命周期变长,只有在整个程序走完才会销毁,但作用域不变,出了作用域无法识别


2.修饰全局变量,正常的全局变量在任何.c文件中都可见,而static修饰后,它的属性由外部链接变为内部链接,使得该全局变量只能在本.c文件中可见

3.修饰函数,用static修饰函数后,会改变函数的链接属性,由外部链接变为内部链接,导致此函数只能在本.c文件中可见 


原文地址:https://blog.csdn.net/2301_80555259/article/details/145105584

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!