C语言再学习之基础语法深挖


内联函数 —— C 中关键字 inline

调用函数时,一般会由于建立调用、传递参数、跳转到函数代码并返回等花费掉一些时间,而且一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。

为了解决这个问题,在C99中特别地引入了inline修饰符,即内联函数。

关键字 inline 告诉编译器,任何地方只要调用内联函数,就直接把该函数的机器码插入到调用它的地方,类似于带参宏。

inline int max (int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

a = max (x, y); // 等价于 "a = (x > y ? x : y);"

结构体 struct ——构造数据类型

结构体(struct)就是一种把一些数据项组合在一起的数据结构类型,定义一个个人信息的结构体如下:

struct Info{
    char name[10];
    double height;
    int age;
};
//定义一个 Info 类型的结构体变量
struct Info Mahoo;
//还可以用 typedef 
typedef struct Info sInfo;
sInfo Mahoo;

将上述代码融合一下,常用来定义结构体的代码如下:

typedef struct Info{
    char name[10];
    double height;
    int age;
}sInfo;
sInfo Mahoo;

结构体的对齐问题

struct Info1{
    char name[3];
    double id;
    int age;
};
struct Info2{
    double id;
    char name[3];
    int age;
};

//定义一个 Info 类型的结构体变量
struct Info1 Mahoo1;
struct Info2 Mahoo2;

int main(){
    printf("Mahoo1:%d,Mahoo2:%d",sizeof(Mahoo1),sizeof(Mahoo2));
    return 0;
}

代码输出为:Mahoo1:24,Mahoo2:16,可见同样的结构体内容,所占的字节大小是不一样的,而且在64位的编译器中,char :1个字节,int: 4个字节,double: 8个字节,按理来说都应该是15个字节才对,因为涉及到字节对齐的缘故,在特定类型变量存储的时候,各种类型数据是按照一定的规则在空间上排列,而不是顺序的一个接一个的存放。

首先要明确编译器的对齐原则和一些基本概念:

  • 对齐:假定从零地址开始,每成员的起始地址编号,必须是它本身字节数的整数倍。其次,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍);

  • 数据类型自身的对齐值:char型为1,short为2,int,float为4,double类型为8,单位字节。在Linux系统下计算时超过4字节按4字节计算;

  • 结构体的自身的对齐值:取决于其成员中自身对齐值最大的那个值;

  • 指定对齐值:通过#pragma pack (value)时动态地确定指定对齐值 value;

  • 数据成员和结构体的有效对齐值:自身对齐值和指定对齐值中较小的那个值,且结构体数据起始地址%有效对齐值 = 0 ;

好的,现在就拿上面的两个结构体例子来说,首先假定地址是从零开始的,在 Info1 中,第一个成员char name[3],其对齐值为 1,占三个字节;所以第二个成员double id是从

3 开始,由于对齐的原则,该成员的有效对齐值为 8(无指定对齐值默认因为 8),即double id真正的地址应该满足%8 = 0,也就是对齐到 8 这个地址;第三个成员变量int age起始地址为 16,有效对齐值为自身对齐值 2,所以存储在 1619 地址。到这里结构体 Info1 变量的sizeof()应该为地址019共20个字节,但还要考虑到结构体的有效对齐值(自身对齐值为 double id的自身对齐值 8 ,指定对齐值未定)为 8 ,保证圆整,所以向上取整到 24%8 = 0。

Info2 也是按一样规则的对齐, double id地址为07,char name[3]为811,int age为12~16,所以 Info2 变量的sizeof()为16。

对齐问题的启发

由上面的例子可以看出,不同的成员变量声明顺序有时决定了结构体不同的大小,如果想要节约空间的话,则可以把结构体中的变量按照类型大小从小到大声明(很容易得出),尽量减少中间的填补空间。

常量 const —— 一个不能被改变的普通变量

const 一般用于定义常量或常量指针,所谓常量也就是不可更改的意思,但部分情况可以通过指针修改;当 const 修饰指针时,有两种情况:

  • 修饰一个指向常量的指针,则指针是可变的,常量不可变,const 在 * 的左侧;
  • 修饰指针为常量,即常量指针,指向的对象是可变的,const 位于 * 的右侧;
// 修饰常量
int const num;
const int num;
// 修饰指向常量的指针
const int *p;
int const *p;
// 修饰指针常量
int * const p;
// 都为常量
const int * const p;

另一种情况是修饰函数的形参,说明形参在函数内部不会被改变。例如修饰传递的指针形参时,不会修改实参指针所指向的数据:

void foo(int * const p);

文章作者: Mahoo Huang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mahoo Huang !
评论
 上一篇
简单入门CSS预处理器Less和Sass 简单入门CSS预处理器Less和Sass
Sass和Less都属于CSS预处理器,CSS预处理器定义了一种新的语言,其基本的思想是,用一种特殊的语言,为CSS增加一些编程的特性,如变量、语句,函数、继承等概念。将CSS作为目标生成文件,然后开发者就只要使用这种语言进行CSS的编码开
2020-03-08
下一篇 
利用FSMC模拟8080时序控制LCD 利用FSMC模拟8080时序控制LCD
注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出! ILI9341 液晶控制器 ILI9341 控制器内部电路连接完后
2020-02-25
  目录