每日一题(八)

    科技2022-08-03  105

    文章目录

    10.1 内存碎片10.2 数组、字符串10.3 越界读写字符串10.4 malloc内存泄漏+free非法访问+二级指针传参10.5 求含位域的结构体大小

    10.1 内存碎片

    内部碎片的产生因为所有的内存分配必须起始于可被 4、8 或 16 整除(视 处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设当某个客户请求一个 43 字节的内存块时,因为没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。

    外部碎片的产生: 频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。假 设有一块一共有100个单位的连续空闲内存空间,范围是0~ 99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。这时候你 继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10~14区间。如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比 如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是09空闲,1014 被占用,1524被占用,2599空闲。其中09就是一个内存碎片了。如果1014一直被占用,而以后申请的空间都大于10个单位,那么0~9就 永远用不上了,变成外部碎片。

    10.2 数组、字符串

    char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char *str5 = "abc"; const char *str6 = "abc"; char *str7 = "abc"; char *str8 = "abc"; cout << ( str1 == str2 ) << endl; cout << ( str3 == str4 ) << endl; cout << ( str5 == str6 ) << endl; cout << ( str7 == str8 ) << endl;

    判断如上程序的输出: 0 0 1 1

    原因是str1、2、3、4都是数组变量,strx代表数组的首地址,而数组里面的内容都是栈上面的,所以指向各不相同;但是str5、6、7、8是指针变量,指向的是字符串常量,相同的字符串在常量区的地址都是一样的。

    10.3 越界读写字符串

    分析如下程序:

    int main (void) { char a = 'a'; char *str = &a; strcpy(str, "Hello C"); printf(str); return 0; }

    指针str指向变量a的地址,后面又将字符串“Hello C”复制在指针所指向地址上,虽然可能正常输出结果,但是会因为越界读写内存造成程序崩溃。

    10.4 malloc内存泄漏+free非法访问+二级指针传参

    程序如下:

    #include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory(char *p) { p = (char*)malloc(100); strcpy(p, "Hello world"); } int main(void) { char *str = NULL; GetMemory(str); printf("%s\n", str); free(str); return 0; }

    程序会崩溃,原因:malloc指向了新地址导致内存泄漏和free导致访问非法内存

    首先要明白如下概念:

    C函数的所有参数都是传值调用,即函数会获得参数值的一份拷贝!!!即函数可以修改传递进来的参数值,而不必担心会影响到调用程序实际传给函数的参数值。

    如果参数是数组名,函数中就可以通过访问数组下标来修改数组中的元素,这样岂不是函数可以修改调用程序实际传给函数的参数值吗?这样岂不是与上面所说:函数获得的只是参数值的一份拷贝,不会修改原参数值的说法吗?

    实际上这种情况也是符合上述说法的,数组参数传递的是数组名,实际数组名的值就是一个指针,指针的值就是地址,强调一下,是地址!!!所以函数获取的还是数组地址的一份拷贝,在函数中通过这份拷贝的地址同样可以访问到原数组中的元素,从而修改数组的元素。

    回归到程序上:指针p传递的其实就是str地址的一份拷贝,在函数中修改了p指针的指向(指向malloc函数分配内存空间),对原来的str指针造不成丝毫影响。因为没有在str指向的地址上进行修改,而是在新的地址上进行修改(strcpy),所以对原来的str所指向的地址空间造不成任何影响。即str指向的还是NULL,但是对一个NULL指针进行free操作就是非法访问了。而且在malloc之后没有进行free释放内存空间,也造成了内存泄漏。

    解决方法:

    使用二级指针传递参数,什么时候用到二级指针呢?就是在函数外定义一个指针,在函数内给指针赋值,要求函数结束后指针的赋值生效,这样就需要二级指针!

    对于参数传递来说,传递进入函数的始终是参数的一个副本(拷贝值)。所以一级指针作为参数时,传入的是指针指向地址的副本,随后malloc改变的是副本的值,malloc作用不到原始指针;二级指针作为参数时,传入的是指针变量本身地址的副本,malloc改变的是基于原指针地址上的指向的地址,所以可以作用到原指针;

    修改后的程序如下:

    #include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory(char **p) //参数 char **p 传递的是指针变量的地址 { *p = (char*)malloc(100); //malloc修改的是基于原指针变量的指向的地址,所以可以作用到原指针 strcpy(*p, "Hello world"); } int main(void) { char *str = NULL; GetMemory(&str); //传入的是指针变量的地址 printf("%s\n", str); free(str); //释放内存空间 str = NULL; return 0; }

    free:

    传递给free的指针必须是从malloc、calloc、realloc返回的指针,否则让free释放一块非动态分配的内存可能导致程序立即终止或者晚些时候终止。有时候使用free释放一块动态分配的内存的一部分也可能引起这种错误。注意:不要访问free释放的内存。

    内存泄漏:

    内存泄漏是指内存被动态分配之后,当它不再使用时未被释放就会造成内存泄漏。内存泄漏回增加程序的体积,还有可能导致程序或者系统的崩溃。

    10.5 求含位域的结构体大小

    struct s1 { int a : 8; int b : 4; int c : 3; double d; }; struct s2 { int a : 8; int b : 4; double c; char d : 3; }; struct s3 { int a : 5; int b : 6; };

    s1 = 2+6+8=16

    a、b、c一共占2Byte;前三个对齐后再加6Byte;d占8Byte

    s2 = 2+6+8+8=24

    a、b一共占2Byte;对齐后再加6Byte;c占8Byte;d对齐后占8Byte

    s3 = 1+1+2=4

    对齐后占4Byte

    Processed: 0.009, SQL: 8