结构体字节对齐
尽可能让结构体的每一个成员都占据一个内存单元,提高程序运行的速度。
struct student {
unsigned char age;
double x;
unsigned short score;
};
sizeof (struct student) // 24(8字节对齐)
_Alignof
运算符(C11)
获取对齐的字节数
_Alignof (类型名)
_Alignof (表达式)
示例
struct student {
unsigned char age;
double x;
unsigned short score;
};
_Alignof (struct student) // 8
示例2
struct student {
unsigned char age;
unsigned short score;
};
_Alignof (struct student) // 2
联合体(Union)
联合体也叫共用体,他的定义和 结构体一样,只是他的每个成员变量都使用共同的内存(起点地址相同)。
union 联合体类型名 {
成员类型1 成员变量1;
成员类型2 成员变量2;
...
}
示例
#include <stdio.h>
union u_t{
unsigned char buf[2];
unsigned short s;
};
int main(int argc, char * argv[]) {
union u_t u;
printf("sizeof(u):%ld\n", sizeof(u));
u.s = 258; // 00000001 000000002
printf("u.s:%#x, u.buf[0]:%#x, u.buf[1]:%#x\n",
u.s, u.buf[0], u.buf[1]);
// 0x0102 0x02 0x01
return 0;
}
大小端
大小端(Endianness)是指计算存储多字节数据(如:short int、int、float、double等)时,各个字节在内存中的排列顺序。
小端模式
低位字节存储在低地址,高位字节存储在高地址。
大端模式
低位字节存储在高地址,高位字节存储在低地址
如:
unsigned int x = 0x12345678;
小端表示
+------+------+------+------+
| 0x78 | 0x65 | 0x43 | 0x21 |
+------+------+------+------+
低地址 高地址
大端表示
+------+------+------+------+
| 0x21 | 0x43 | 0x65 | 0x21 |
+------+------+------+------+
低地址 高地址
检测方法
使用联合体进行检测,如上图所示,定义一个联合体
union u_t {
unsigned char buf[4];
unsigned int x;
}
将 整数 0x87654321
赋值给 x
, 检查 buf[0]
是 0x21
就是小端,如果是 0x87
就是大端。
枚举类型
枚举类型(Enumeration) 是用于将一组整数常量用自定义类型来表达,使代码更加易读和维护。
语法
enum 枚举类型名 {
标识符1 [= 整数值1], // 第一个默认值是0
标识符2 [= 整数值2], // 后续默认值是前一个加1
...
};
示例
#include <stdio.h>
enum week {
Mon = 1,
Tue,
Wed,
Tru,
Fri,
Sat,
Sun
};
int main(int argc, char * argv[]) {
// 定义枚举类型的变量
enum week w = Mon;
printf("Sun:%d\n", Sun);
printf("w:%d\n", w);
return 0;
}
作用:
- 提到可读性
- 自动分配整数值
- 限定取值范围
注意:
给枚举类型的变量赋值整数,有些编译器可能报错或警告。
计算机中常用的段
代码段
数据段
堆
栈
动态内存分配
动态内存分配是指从堆中动态的获取和释放内存。
常用函数
// 申请内存,给定字节数,返回内存的首地址,失败返回NULL。
void *malloc(size_t size);
// 释放内存,还回给系统。
void free(void *ptr);
// 申请内存,给定每个成员字节数和成员数量,返回内存的首地址。内存内容置零。
void *calloc(size_t nmemb, size_t size);
// 释放旧内存,返回新内存地址。
void *realloc(void *ptr, size_t size);
头文件: #include <stdlib.h>
示例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
int * pint = NULL; // 定义一个指针,用来指向新分配的内存。
// 申请 4 字节的内存用来存储 int 类型数据
pint = (int*)malloc(4);
if (pint != NULL) {
printf("申请内存成功\n");
// 将 数值 100 放入这个内存
*pint = 100;
printf("内存存储的是:%d\n", *pint);
if (pint) {
free(pint);
pint = NULL;
}
} else {
printf("申请内存失败\n");
}
return 0;
}
在动态申请内存时,通常用指针记录内存的位置。通常指针为空则说明没有指向可使用的内存,不为空是则说明指向了已经分配的内存。
如果内存已经释放,则应及时将指针置空,否则将会成为悬空指针。
悬空指针:
悬空指针是指向不可用地址的指针。
内存碎片问题:
多次分配和释放内存,可能回导致没有大的连续的内存可用,导致内存分配失败。
注意事项:
- 使用 malloc分配的内存未进行初始化,其内部可能是任意值,需要手动初始化。
- malloc、calloc、realloc分配的内存必须使用 free 显式的释放。
- 不要重复释放内存,对已经释放内存的指针再次调用free释放回导致致命结果。
- malloc、calloc、realloc内存分配函数可能失败,失败返回NULL
示例
写一个程序,输入学生的数量n,使用动态内存分配的方法申请 n 个学生的需要的内存,存储 n 个学生的姓名、年龄和成绩。
struct student {
char name[50];
int age;
int score;
};
- 输入n 个学生的信息,
- 打印 n 个学生的信息。
参考答案:
#include <stdlib.h>
struct student {
char name[50];
int age;
int score;
};
int main(int argc, char * argv[]) {
int n;
struct student *pstu = NULL;
// struct student pstu[20];
printf("请输入人数:");
scanf("%d", &n);
// 申请内存
pstu = (struct student*)malloc(sizeof(struct student) * n);
if (pstu == NULL)
return -1;
// 输入
for (int i=0; i < n; i++) {
printf("请输入姓名:");
scanf("%s", pstu[i].name);
printf("请输入年龄:");
scanf("%d", &pstu[i].age);
printf("请输入成绩:");
scanf("%d", &pstu[i].score);
}
// 打印
for (int i=0; i < n; i++) {
printf("姓名:%s ", pstu[i].name);
printf("年龄:%d ", pstu[i].age);
printf("成绩:%d\n", pstu[i].score);
}
free(pstu);
pstu = NULL;
return 0;
}
函数指针
函数是代码段的一段CPU指令,函数名是一个常量地址,是该函数的起始地址。使用指针可以指向这段地址,并可以调用该函数。
作用:
使用指针调用函数。
指向函数的指针称为函数指针:
定义语法
返回类型 (*指针变量名)(形参类型1, 形参类型2, 形参类型3);
示例
#include <stdio.h>
int myadd(int x, int y) {
printf("myadd(%d, %d) is called\n", x, y);
return x + y;
}
int mymul(int x, int y) {
printf("mymul(%d, %d) is called\n", x, y);
return x * y;
}
int main(int argc, char * argv[]) {
// 定义函数指针
int (*p_fun)(int,int) = mymul;
// 使用函数指针来调用指向的函数
p_fun(100, 200);
p_fun = myadd;
p_fun(300, 400);
return 0;
}
复杂指针的定义方法
使用 (*)
替换变量名的办法:
// 指向一维数组的指针
int arr[10];
int (*arr)[10];
// 指向二维数组的指针
int * arr2d[3][4];
int * (*parr2d)[3][4];
// 指向函数的指针
int myadd(int x, int y);
int (*pmyadd)(int, int);
int x;
int *px;
int a[3][4];
a返回的类型为 int (*p) [4];
复杂指针的类型别名 typedef
// 指向函数的指针的类型别名
#include <stdio.h>
int myadd(int x, int y) {
printf("myadd(%d, %d) is called\n", x, y);
return x + y;
}
int mymul(int x, int y) {
printf("mymul(%d, %d) is called\n", x, y);
return x * y;
}
// 定义int(int,int) 类型函数的别名 PFUN;
typedef int(*PFUN)(int,int);
int main(int argc, char * argv[]) {
// 定义函数指针
// int (*p_fun)(int,int) = mymul;
PFUN p_fun = mymul; // 等同于上述函数指针变量的定义
// 使用函数指针来调用指向的函数
p_fun(100, 200);
p_fun = myadd;
p_fun(300, 400);
return 0;
}