C语言基础六(结构体以及一些宏定义)

时间:2023-01-29 17:31:09

结构体

结构体声明时,成员大小必须确定,因此不能有未定义大小的数据类型作为成员,同样也不能用函数作为结构体成员。
但是,每个系统指针类型的大小是确定的,所以可以以结构体指针或者函数指针作为结构体成员。

结构体字节对齐

结构体变量本身从4字节对齐的位置开始存放。
整个结构体所占空间还要是成员中‘占字节最大的基本数据类型’的整数倍,不够的在末尾补齐。
short类型从地址是2的倍数处开始存储,int类型从地址是4的倍数处开始存储,double类型从地址是8的倍数处开始存储,空出来的部分内存由编译器填充。

struct da
{
double a;
char b;
};
struct data //24字节 ,内部结构体对齐规则会影响外部结构体
{
struct da d; //16字节
int e; //8字节
};
struct da
{
int a;
int g;
char b;
};
struct data //24字节,外部结构体对齐规则不会影响内部结构体
{
struct da d; //12字节
char e; //4字节
double f; //8字节
};

柔性数组

在结构体内的一个数组,必须是结构体最后的一个元素,除了这个数组,结构体还必须至少有一个成员
特定形式:a[ ]或a[0]。并且这个数组不占空间,只是一个符号(代表一个常量地址)。
一般情况下,结构体内会有一个成员专门表示柔性数组的元素个数。

typedef struct
{
int len;
int arr[];
}S;

对齐指令

#pragma pack(n) //(1、2、4、8、.....)
#pragma pack()

这两个配合使用,表示一个区间,只有这个区间内的结构体按照n字节对齐。
设置为1,就是不对齐
如果n设置的小,充分利用内存空间,牺牲了速度,降低了访问效率。
如果n设置的大,提高效率、性能,牺牲了内存空间。
用这个指令指定的对齐方式和结构体自身默认对齐方式,俩者取最小的。

位字段

专用于结构体,有时侯,结构体成员表示的数据很小,就用几个位来表示。
结构体成员的类型必须是 int 或者 unsigned int,单个成员大小不能超过一个int大小(32位)
注意 : 字段不可取地址,因为地址最小单位为字节。

struct data
{
unsigned a : 1; // 1就是一个bit,范围:0~1,超出表示范围时,自动截取低位
unsigned b : 2; // 2就是er个bit,范围:0~3
unsigned c : 2; // 2bit
}s, *p = &s;
//一个字段 32位 就是一个int
struct data1
{
unsigned a : 1;
int : 12; // 无名字段,不可访问,只是占位
int : 0; // 0字段,不可访问,只是占位 整个字剩下的位,全部写0
unsigned b : 2;
}s1;

//位字段:下一个字段不够在剩余的bit存放时,必须另起一个字。字段不能跨字。
struct data3 //占2个int
{
int a : 2;
int b : 32;
}s2;

typeof() 关键字

专门用于获得类型,()里可以放变量名,或者表达式

int *p;
typeof(p) a; //相当于 int *a
typeof(*p) b; //相当于 int a

检查系统错误的宏

#include <stdio.h>
#include <stdlib.h>
//perror()检查系统错误.一旦发生了,系统错误就会产生一个错误数字(errno),对应相应的错误字符串。
//标准C定义了两个宏 EXIT_SUCCESS 0和 EXIT_FAILURE 1或-1,可以作为exit()的参数,来分别指示是否为成功退出。
//exit(参数)传递给的是父进程,或者shell终端

#define handle_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
//用do{}while(0),是为了宏定义多条语句,并可以匹配到使用宏定义时后面的分号

int main(void)
{
//文件描述符,没有负数
int fd = -1;
int ret = close(fd);
if (-1 == ret)
handle_error("file error!");

寄存器

编译器在计算表达式时,只要没有 赋值运算 或者 指向运算,就只会在cpu和寄存器之间进行计算,并不会对内存操作。

unsigned short a = 0x1234;      
unsigned char * p1 = (unsigned char *)&a, i = 8;

printf("sizeof(*p1 << i) = %d.\n", sizeof(*p1 << i)); //结果为4
printf("0x%x.\n", *p1 << i); //输出为3400,被暂时存放在一个4字节寄存器中,等待下一步计算
printf("0x%x.\n", *(p1+1)); //输出为12,12的值并没有被34覆盖
printf("0x%x.\n", *p1 <<= i); //输出为0,将寄存器中的最低位赋值给p1所指向的空间

关于结构体的两个内核宏

linux内核里的两个宏:在驱动应用中很广泛。
off_set_of(type, member) 计算结构体内元素的偏移量
containe_of(ptr, type, member) ptr是结构体里成员的指针,这个宏计算出结构体的首地址
包含两句代码(表达式),必须要加{}.
这两个宏:内核双链表。

#define off_set_of(type, member) ((int)&(((type *)0)->member))
//得到成员相对于结构体首地址的偏移字节大小
//将0地址开始的这段空间强制转化为结构体类型,这时(type *)0是指向这个结构体首地址的指针,
//指向member成员时,再取地址,&(((type *)0)->member))表示的是member成员的地址值。
//member成员的地址值减去结构体首地址值,就是成员地址的偏移量,结构体首地址为0.
//此时得到的差值再强制类型转换为一个int型数据,64位为long int型
//因为没有赋值和指向运算,所以不对内存操作,也就不会出现非法内存访问, 或者说 段错误。

#define container_of(ptr, type, member) ({typeof(((type *)0)->member) *_mptr = ptr; \
(type *)((char *)_mptr-off_set_of(type, member));})
//通过成员的指针得到该成员所在结构体的首地址
//1、typeof()得到结构体成员变量的类型 2、指针赋值,得到真实成员变量的地址值
//3、减去该成员偏移量得到一个数字,该数字和结构体本身首地址在数值上一样
//4、最后强制类型转换为结构体指针类型

网络结构体

#include <netinet/in.h>

struct in_addr
{
in_addr_t s_addr;
};

/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); /* AF_INET IPv4 Internet protocols 指定网络地址类型*/
in_port_t sin_port; /* Port number. 端口号 16位无符号short */
struct in_addr sin_addr; /* Internet address. 具体的网址32位无符号int*/

/* Pad to size of `struct sockaddr'. */
//这些空间,可能是系统固定配置,不能改变,只需要留出足够的空间即可,也有可能是预留出的空间,以便日后扩充。
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};

网络结构体赋值

//htons()和inet_addr()函数都是库函数,用来将本地地址和端口号转换为网络地址和端口号,
//网络上数据一般是大端存储
//大端序(Big Endian):高位字节存放到低位地址(高位字节在前)。
//小端序(Little Endian):高位字节存放到高位地址(低位字节在前)
void set_net_struct(struct sockaddr_in *p)
{
p->sin_family = AF_INET;
p->sin_port = htons(PORT);
p->sin_addr.s_addr = inet_addr(ADDR);

枚举类型enum

枚举:定义的是常量符号,相当于宏定义常数的一个集合

enum  fangxiang   // 标识符
{
EAST, //成员大写,用逗号隔开
WEST = 99,
SAUTH,
NORTH = 100,
}E;

默认定义的常数从0开始,EAST = 0,可以设置成员所代表的数值,
设置数值的成员,下一个成员数值等于上一个成员+1。
可以用于状态机密码锁。

typedef enum STATE
{
STATE1,
STATE2,
STATE3,
STATE4,
STATE5,
STATE6,
STATE7,
}S;

int main(void)
{
int num = 0;
//1、密码锁初始状态
S current_state = STATE1;
// 输入密码,进行解锁
printf("输入一个密码数字:");
while (1)
{
scanf("%d", &num);
printf("num = %d.\n", num);
//解锁子开始
switch (current_state)
{
case STATE1:
if (num == 9)
current_state = STATE2; // 用户每输对一次,进入下一状态
else
current_state = STATE1;
break;
case STATE2:
if (num == 5)
current_state = STATE3; // 用户每输对一次,进入下一状态
else
current_state = STATE1;
break;
case STATE3:
if (num == 2)
current_state = STATE4; // 用户每输对一次,进入下一状态
else
current_state = STATE1;
break;
default:
current_state = STATE1;
break;
}
if (current_state == STATE4)
{
printf("开了锁,请进!.\n");
return 0;
}
if (current_state == STATE1)
printf("初始状态.\n");
}
return 0;
}