c语言判断变量类型6,C语言基础六(结构体以及一些宏定义)

结构体

结构体声明时,成员大小必须确定,因此不能有未定义大小的数据类型作为成员,同样也不能用函数作为结构体成员。

但是,每个系统指针类型的大小是确定的,所以可以以结构体指针或者函数指针作为结构体成员。

结构体字节对齐

结构体变量本身从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

#include

//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

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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值