一.用define宏定义表达式
1.定义一年有多少秒:
#define SEC_A_YEAR 60*60*24*365
//上述描述不可靠,没有考虑到在16位系统下把这样一个数赋给整型变量的时候可能会发生溢出
#define SEC_A_YEAR (60*60*24*365)UL
2.#undef
#undef是用来撤销宏定义的
#define PI 3.1415926
code...
#undef PI
//接下来的代码无法使用宏PI
//也就是说宏的生命周期从#define开始到#undef结束。
3.内存对齐
我们可以使用#pragma pack()来改变编译器的默认对齐方式。
使用指令#pragma pack(n),编译器将按照n个字节对齐。
使用指令#pragma pack(),编译器将取消自定义字节对齐方式。
虽然指定了按n字节对齐,但并不是所有的成员都是以n字节对齐。对齐的规则是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是n个字节)中较小的一个对齐。
如
#pragma pack(8)
struct TestStruct1 {
char a;
long b;
}
struct TestStruct2 {
char c;
TestStruct1 d;
long long e;
}
#pragma pack()
#include<stdio.h>
//#pragma pack(2) //TestStruct1 6 TestStruct2 16
//#pragma pack(4) //TestStruct1 8 TestStruct2 20
#pragma pack(8) //TestStruct1 8 TestStruct2 24
//24的布局是当填充到e元素时前面已经使用了12个字节,但是后面的long long类型
//为8个字节,避免对这8个字节进行两次存取,所以需要填充4个字节,接着在放入long long类型。
//c TestStruct1.a TestStruct1.b TestStruct2.e
//1*** 1*** 1111 **** 1111 1111
struct TestStruct1 {
char a;
long b;
};
struct TestStruct2 {
char c;
struct TestStruct1 d;
long long e;
};
int main(){
printf("sizeof(TestStruct1) = %d\n", sizeof(struct TestStruct1));
printf("sizeof(TestStruct2) = %d\n", sizeof(struct TestStruct2));
}
#pragma pack()
总结一下:
a.每个成员分别按自己的方式对齐,并能最小化长度;
b.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度;
c.对齐后的长度必须是成员中最大的对其参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。
**二.int p = NULL 和 p = NULL有什么区别
//此时我们通过编译器查看p的值为0x00000000
int *p = NULL;
//下面这个可能就存在非法访问的问题了
//因为没有指定p的地址,出现野指针
int *p;
*p = NULL;
三.如何将数值存储到指定的内存地址
int *p = (int *)0x12ff7c;
*p = 0x100;
或者
*(int *)0x12ff7c = 0x100;
四.数组
1.a[5]与sizeof(a[5]):
sizeof(a[5])的值在32位系统下为4,并没有错,主要是因为sizeof是关键字不是函数,函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。所以这里并没有真正去访问a[5].
2.&a[0]和&a的区别
a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。
3.数组名a作为左值和右值的区别
x=y
左值:在这个上下文环境中,编译器认为x的含义是x所代表的地址。
右值:在这个上下文环境中,编译器认为y的含义是y所代表的地址里面的内容。
当a作为右值的时候代表是数组的首地址吗,其实这并不是正确的,a作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组的首地址。仅仅只是代表并没有一个地方来存储这个地址,也就是说编译器并没有为数组a分配一块内存来存其地址。
a不能作为左值。
4.以指针的形式访问和以下标的形式访问指针
#include<stdio.h>
int main() {
char *p = "abcdef";
char a[] = "123456";
//本质上以指针的形式访问和以下标的形式访问指针
//是没有区别的,下标的访问也是先取出p里存储的地址值
//然后再偏移4个元素去访问。
printf("p[4]=%c\n",p[4]);
printf("*(p+4)=%c\n",*(p+4));
return 0;
}
5.a和&a的区别
#include<stdio.h>
int main() {
int a[5] = {1,2,3,4,5};
int *ptr = (int *)(&a+1);
printf("%d,%d\n",*(a+1),*(ptr-1)); //2,5
//p3和p4都是数组指针,指向的是整个数组,&a是整个数组的首地址
//a是数组首元素的首地址。其中p3两边的数据类型完全一致的。而p4
//两边的数据类型就不一致的,左边的类型是指向整个数组的指针,右边
//的数据类型是指向单个字符的指针。所以一般编译器会报警告的。
char a[5] = {'A','B','C','D'};
char (*p1)[5] = &a;
char (*p2)[5] = a;
printf("%s,%s\n",p1+1,p2+1);
char (*p3)[3] = &a;
char (*p4)[3] = a;
printf("%s,%s\n",p3+1,p4+1);
char (*p5)[10] = &a;
char (*p6)[10] = a;
printf("%s,%s\n",p5+1,p6+1);
//结语:由于&a和a的值是一样的,而变量作为右值时编译器只是取变量的值
//所以运行并没有什么问题。不过最好不要这么使用。
return 0;
}
6.int (*)[10] p
int (*)[10]p 其实和int (*p)[10]一样都是定义数组指针的。其实数组指针的原型就是这样的。
7.地址的强制转换
#include<stdio.h>
int main() {
struct Test{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
printf("p = %p\n", p);
printf("sizeof(*p) = %d\n", sizeof(*p));
//指针变量与一个整数相加减并不是用指针变量里的地址
//直接加减这个整数。这个整数的单位不是byte而是元素的个数
printf("p+0x1 = %p\n", p+0x1);
printf("(unsigned long)p+0x1 = %p\n", (unsigned long)p+0x1);
printf("(unsigned int *)p+0x1 = %p\n", (unsigned int*)p+0x1);
return 0;
}
五.无法向函数传递一个数组
#include<stdio.h>
//C语言中,当一维数组作为函数参数的时候,编译器总是
//把它解析成一个指向其首元素首地址的指针。
//void fun(char a[10]) {
//void fun(char *a) {
void fun(char a[]) {
char c = a[3];
printf("sizeof(a) = %d\n", sizeof(a));
printf("c = %c\n", c);
}
int main() {
char b[10] = "abcdefg";
fun(b);
return 0;
}
六.无法把指针变量本身传递给一个函数
#include<stdio.h>
void fun(char *p) {
char c = p[3];
p[2] = 'f';
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("p = %s\n", p);
printf("sizeof(a) = %d\n", sizeof(p));
printf("c = %c\n", c);
}
int main() {
char b[10] = "abcdefg";
fun(b);
printf("b = %p\n", b); printf("&b = %p\n", &b);
printf("b = %s\n", b);
return 0;
}
因为无法把指针变量本身传递给一个函数
应该对实参做一份拷贝并传递给被调用函数,即对b做一份拷贝,假设其拷贝名为_b,那传递到函数内部的就是_b,而并非b本身。
七.无法把指针变量本身传递给一个函数
#include<stdio.h>
void GetMemory(char *p, int num)
{
p = (char *)malloc(num*sizeof(char));
}
int main()
{
char *str = NULL;
GetMemory(str, 10);
printf("str = %p\n", str);
strcpy(str,"hello");
free(str); //free 并没有起作用,内存泄漏
return 0;
}
在运行strcpy(str, “hello”)语句的时候会发生错误,这时候str的值仍然为NULL,我们malloc的内存的地址并没有赋给str,
而是赋给了_str.而这个_str是编译器自动分配和回收的,我们根本就无法使用。
方法一:利用return
#include<stdio.h>
char * GetMemory(char *p, int num)
{
p = (char *)malloc(num*sizeof(char));
return p;
}
int main()
{
char *str = NULL;
str = GetMemory(str, 10);
printf("str = %p\n", str);
strcpy(str,"hello");
free(str); //free 并没有起作用,内存泄漏
return 0;
}
方法二:利用二级指针
#include<stdio.h>
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num*sizeof(char));
}
int main()
{
char *str = NULL;
GetMemory(&str, 10);
printf("str = %p\n", str);
strcpy(str,"hello");
free(str); //free 并没有起作用,内存泄漏
return 0;
}
八.二维数组参数与二维指针参数
void fun(char a[3][4]);
void fun(char a[][4]);
我们完全可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4个char类型数据的数组。
C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。
也就是说我们可以把函数声明改写成:
void fun(char (*p)[4])
二维数组参数和二维指针参数的等效关系
C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维
再也不可改写。比如: a[3][4][5]作为参数时可以被改写为(*p) [4][5]
九.函数指针数组
#include<stdio.h>
char *fun1(char *p) {
printf("[%s:%d] %s\n",__func__, __LINE__, p);
return p;
}
char *fun2(char *p) {
printf("[%s:%d] %s\n",__func__, __LINE__, p);
return p;
}
char *fun3(char *p) {
printf("[%s:%d] %s\n",__func__, __LINE__, p);
return p;
}
int main()
{
char *(*pf[3])(char *p);
pf[0] = fun1;//可以直接用函数名
pf[1] = &fun2;//可以用函数名加上取地址符
pf[2] = &fun3;
pf[0]("fun1");
pf[1]("fun2");
pf[2]("fun3");
return 0;
}
十.避免野指针
方法就是在定义指针变量的时候同时最好初始化为NULL。用完指针之后也将指针变量的值设置为NULL.
十一.常见的内存错误及对策
1.指针没有指向一块合法的内存
1.1 结构体成员指针未初始化
#include<stdio.h>
struct student {
char *name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name, "Jimy");
stu.score = 99;
printf("stu.score = %d\n", stu.score);
printf("stu.name = %s\n", stu.name);
return 0;
}
这里定义了结构体变量stu,但是没想到这个结构体内部char*name这成员在定义结构体变量stu时。只是给name这个指针变量本身分配了4个字节。name指针并没有指向一个合法的地址,所以在调用strcpy函数时,会将字符串Jimy往这个非法的内存拷贝,所以会导致出错。解决的办法就是为name指针malloc一块空间。
#include<stdio.h>
struct student {
char *name;
int score;
}stu,*pstu;
int main()
{
pstu = (struct student *)malloc(sizeof(struct student));
strcpy(pstu->name, "Jimy");
pstu->score = 99;
printf("pstu->score = %d\n", pstu->score);
printf("pstu->name = %s\n", pstu->name);
free(pstu);
return 0;
}
上面同样也是没有给name指针分配内存。
1.2没有为结构体指针分配足够的内存
#include<stdio.h>
struct student {
char *name;
int score;
}stu,*pstu;
int main()
{
//这里误把sizeof(struct student)写成sizeof(struct student *)
pstu = (struct student *)malloc(sizeof(struct student *));//4
pstu->name = (char *)malloc(32);
strcpy(pstu->name, "Jimy");
pstu->score = 99;
printf("sizeof(struct student *)= %d\n", sizeof(struct student *));
printf("pstu->score = %d\n", pstu->score);
printf("pstu->name = %s\n", pstu->name);
free(pstu->name);
free(pstu);
return 0;
}
1.3函数的入口校验
不管什么时候,我们使用指针之前一定要确保指针是有效的。
2.为指针分配的内存太小
为指针分配了内存,但是内存大小不够,导致出现越界错误。
char *p1 = "abcdefg";
char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
strcpy(p2,p1);
//p1是字符串变量,其长度为7个字符,但其所占内存大小为8个byte。
//初学者往往忘记了字符串常量的结束标志\0
char *p2 = (char *)malloc(sizeof(char)*strlen(p1) + 1*sizeof(char));
//只有字符串常量才有结束标志符的,下面的就没有的
char a[7] = {'A','B','C','D','E'};
3.内存分配成功,但并未初始化
所以在定义一个变量时,第一件事就是初始化,你可以把它初始化未一个有效的值。
如
int i = 10;
char *p = (char *)malloc(sizeof(char));
但是往往这个时候我们还不确定这个变量的初值,这样的话可以初始化为0或NULL.
int i = 0;
char * p = NULL;
如果定义的是数组的话,可以这样初始化:
int a[10] = {0};
或者利用memset函数来初始化为0
memset(a, 0 , sizeof(a));
十二.如何使用malloc函数
1.使用malloc函数申请堆区空间的时候,内存分配失败会失败,函数会返回NULL.
既然malloc函数申请内存有不成功的可能,那我们在使用指向这块内存的指针时,必须用if(NULL != p)
语句来验证内存确实分配成功了的。
2.用malloc函数申请0字节内存
#include<stdio.h>
int main()
{
char *ch = (char *)malloc(0);
*ch = 'a';
printf("ch = %p\n", ch);
return 0;
}
注意申请0字节的内存,函数并不放回NULL,这是if(NULL != p)语句校验将会失效的。
3.内存释放之后
使用free函数之后指针变量p本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL;
p = NULL;
十三.return语句不可返回指向"栈内存"的指针,因为该内存在函数体结束时被自动销毁,
如:
#include<stdio.h>
char *Func(void) {
char str[30] = "tomtom";
return str;
}
int main()
{
char *p = Func();
printf("Func return value = %s\n", Func());
printf("p = %s\n", p);
return 0;
}
str属于局部变量,位于栈内存中,在Func结束的时候被释放,所以返回str将导致错误。