• 深入理解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 之间。

     

  • 相关阅读:
    背水一战 Windows 10 (61)
    背水一战 Windows 10 (60)
    背水一战 Windows 10 (59)
    背水一战 Windows 10 (58)
    背水一战 Windows 10 (57)
    背水一战 Windows 10 (56)
    背水一战 Windows 10 (55)
    背水一战 Windows 10 (54)
    背水一战 Windows 10 (53)
    背水一战 Windows 10 (52)
  • 原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13531066.html
Copyright © 2020-2023  润新知