一.用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.1415926code...#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 1111struct 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.对齐后的长度必须是成员中最大的对其参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。
**二.intp = NULL 和p = NULL有什么区别
//此时我们通过编译器查看p的值为0x00000000int *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 *));//4pstu->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。//初学者往往忘记了字符串常量的结束标志\0char *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将导致错误。
C语言基础知识之define宏定义表达式 undef 内存对齐 a和a的区别 数组知识点 int (*)[10] p 二维数组参数与二维指针参数 函数指针数组 常见的内存错误及对策