深入理解C指针 动态内存

2.1 动态内存分配

  malloc函数的参数指定要分配的字节数。如果成功,它会返回从堆上分配的内存的指针。如果失败则会返回空指针。

sizeof操作符使应用程序更容易移植,还能确定在宿主系统中应该分配的正确字节数。

在释放用struct关键字创建的结构体时也可能发生内存泄漏。如果结构体包含指向动态内存分配的内存指针,那么可能需要在释放结构体之前先释放这些指针。

2.2 动态内存分配函数

2.2.1 使用malloc函数

  malloc函数从堆上分配一块内存,所分配的字节数由该函数唯一的参数指定,返回值是void指针,如果内存不足,就会返回NULL。此函数不会清空或者修改内存,所以我们认为新分配的内存包含垃圾数据。

  函数原型:void * malloc(size_t);

  这个函数只有一个参数,类型是size_t,如果参数是负数就会引发问题。在有些系统中,参数是负数会返回NULL。如果malloc的参数是0,其行为是实现相关的:可能返回NULL指针,也可能返回一个指向分配了0字节区域的指针。如果malloc函数的参数是NULL,那么一般会生成一个警告然后返回0字节。

因为当malloc无法分配内存时会返回NULL,在使用它返回的指针之前先检查NULL是不错的做法。

1.要不要进行强制类型转换

  C引入void指针之前,在两种互不兼容的指针类型之间赋值需要对malloc使用显示类型转换以避免产生警告。因为可以将void指针赋值给其它任意类型指针,所以就不再需要显示类型转换了。但有些开发者认为显示类型转换是不错的做法,因为:

  •  这样可以说明malloc函数的用意。
  •  代码可以和C++(或早期的C编译器)兼容,后两者需要显示类型转换。

2.分配内存失败

  如果声明一个指针但没有在使用它之前为它指向的地址分配内存,那么内存通常会包含垃圾值,这往往会导致一个无效内存引用错误。

  char *name;

  scanf("%s",name); 这里使用的是name所引用的内存,实际这块内存还未分配。报错:使用未初始化的局部变量。

3.为数据类型分配指定字节数时尽量用sizeof操作符。

5.静态、全局指针和malloc

  全局变量的初始化要在 main 函数执行前完成。初始化静态或全局变量时不能调用函数。

  static int *pi = malloc(sizeof(int));   这样会产生一个编译时错误信息,全局变量也一样。

对于静态变量,可以通过后面用一个单独的语句给变量分配内存来避免这个问题。

  static int *pi;
    pi = malloc(sizeof(int));

但是全局变量不能用单独地赋值语句,因为全局变量是在函数和可制行代码外部声明的,赋值语句这类代码必须出现在函数中。

在编译器看来,作为初始化操作符的 = 和作为赋值操作符的 = 不一样。

2.2.2 使用calloc函数

  calloc会在分配的同时清空内存。该函数的原型如下:  

    void * calloc(size_t numElements,size_t elementSize);  numElements:元素数目  elementSize:元素大小

  清空内存的意思是将其内容置为二进制0。函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存
那麽这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。

  calloc函数会根据numElementselementSize两个参数的乘积来分配内存,并返回一个指向内存的第一个字节的指针,如果乘积为0,那么calloc可能返回空指针。如果不能分配内存,则会返回NULL

  下例为pi分配了20字节,全部包含0:

  int *pi = calloc(5, sizeof(int));

不用calloc的话,用malloc函数和memset函数可以得到同样的结果:

  int *pi = malloc(5 * sizeof(int));
    memset(pi, 0, 5 * sizeof(int));

如果内存需要清零可以使用calloc,不过执行calloc可能比执行malloc慢。

2.2.3 使用realloc函数

  realloc函数会重新分配内存,函数原型如下:

  void *realloc(void *ptr, size_t size);  

第一个参数为原内存的指针,第二个参数为请求的大小,返回值为新申请内存的指针。具体情况总结如下:

如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。

 注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针。

2.3 用free函数释放内存

  函数原型:

    void free(void *ptr);

指针参数应该指向由malloc类函数分配的内存地址,这块内存会被返还给堆。

如果传递给free函数的参数是空指针,通常他什么都不做。如果不是分配的内存则行为将是未定义的。

应该在同一层管理内存的分配和释放。比如说,如果是在函数内分配的内存,那么就应该在同一个函数内释放它。

2.3.1 将已释放的指针置为NULL

 如果试图解引用一个已释放的指针,其行为将是未定义的。调用free后给指针赋值NULL表示该指针无效,后续再使用这种指针会造成运行时异常。这种技术目的是解决迷途指针问题。

2.3.2 重复释放

  重复释放是指两次释放同一块内存。重复释放同一块内存会造成运行时异常。

这两种情况都是重复释放,第二种更为隐蔽一些:

  int *pi = (int *) malloc(sizeof(int));
  *pi = 5;
  free(pi);
  ...
  free(pi);

 

  int *p1 = (int*) malloc(sizeof(int));
  int *p2 = p1;
  free(p1);
  ...
  free(p2);

2.3.3 堆和系统内存

  堆的大小可能在程序创建后就规定不变了,也可能可以增长。不过堆管理器不一定会在调用free函数时将内存返还给操作系统。释放的内存只是可供应用程序后续使用。所以,如果程序先分配内存然后释放,从操作系统的角度看,释放的内存通常不会反映在应用程序的内存使用上。

 

如果内存已经释放,而指针还在引用原始内存,这样的指针就被称为迷途指针。迷途指针没有指向有效对象,有时也称为过早释放。

使用迷途指针会造成一系列问题,包括:

  • 如果访问内存,则行为不可预期;
  • 如果内存不可访问,则是段错误;
  • 潜在的安全隐患;

此类迷途指针更难察觉:一个以上的指针引用同一内存区域而其中一个指针被释放。

 

memset函数

  函数原型:原型:extern void *memset(void *ptr, int value, size_t num)
  函数功能:将ptr所指的内存区域的前num个字节都设置为value的ASCII值,然后返回指向ptr的指针。
  函数说明:参数value虽声明为int,但必须是unsigned char,该函数使用unsigned char(一个字节8位)转换填充内存块,所以范围在0 到255 之间。

 

 

 

 

posted @ 2020-08-19 18:37  YangXinYi  阅读(260)  评论(0编辑  收藏  举报
//color="150,150,150"粒子的颜色设置 opacity="1"粒子的透明度 count="100"粒子的个数