zoukankan      html  css  js  c++  java
  • 深入理解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 之间。

     

  • 相关阅读:
    redis quick start
    Distributed processing
    DocFetcher 本机文件搜索工具
    ZeroTier One
    windows下搭建voip服务器
    在公司上wifi
    屏幕录制软件
    openresty vs golang vs nodejs
    DISC测试
    How to implement a windbg plugin
  • 原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13531066.html
Copyright © 2011-2022 走看看