【问题标题】:Safely initialize arrays in C in a generic way以通用方式在 C 中安全地初始化数组
【发布时间】:2011-10-21 06:41:18
【问题描述】:

我编写了一些代码,使用 memset 来初始化内置类型的数组,如 int、short、float,更重要的是,pointers,如

typedef void* slot_t;
#define EMPTY_SLOT (slot_t)NULL
...
int n = 10;
slot_t slots[] = (slot_t[])malloc(sizeof(slot_t)*n)
memset(slots,(int)EMPTY_SLOT,n*sizeof(slot_t));

这段代码在 Linux32 中运行良好,memset 接受 32 位 ints 作为第二个参数(即初始化元素),但它不适用于 Linux64,sizeof(slot_t)>sizeof(int) 和 IIRC,在其他平台memset 接受 char 作为它的第二个参数。我还没有验证我在项目中遇到的任何错误都是由于这个原因,但无论如何,可以肯定的是,如果存在的话,最好采用一种更安全但仍然“通用”的方法。你知道吗?

【问题讨论】:

    标签: c 64-bit 32bit-64bit memset


    【解决方案1】:

    memset 真的想要一个 character 作为值来填充内存——注意它填充 bytewise。所以就说0。如果你愿意,你可以把它放在你的 EMPTY_SLOT 宏中。或者,使用calloc()

    (另外,malloc() 调用的返回类型应该是slot_t *。)

    【讨论】:

    • calloc()memset() 有相同的问题:它将目标设置为全位为零,但语言不保证这是空指针的表示。 malloc 调用的最佳用法是slot_t *slots = malloc(n * sizeof *slots)。 (当然你应该立即验证slots != NULL。)
    • @Keith:如果你想要按值填充,你需要遍历数组并赋值——我认为没有任意大小的洪水填充原语。
    【解决方案2】:

    memset() 的第二个参数是 int 类型,但它指定要存储在目标的每个 byte 中的值——这意味着,如果 sizeof(int) == 4,你将四倍的内存归零。

    零填充slots 数组的方法是

    memset(slots, 0, n * sizeof *slots);
    

    (假设 slots 被正确地声明为指针而不是数组),除了不能保证空指针表示为全位为零(它可能是,但你不应该依赖它)。

    如果您想要完全的可移植性,您需要编写一个循环将每个元素设置为 NULL。

    如果您愿意假设空指针全为零,则可以使用memset——但请务必按​​照我指定的方式调用它。

    【讨论】:

    • slots 是动态分配的,在其上运行sizeof 将产生一个指针大小。
    • @littleadv:已修复(尽管原始代码试图将插槽声明为数组,这让我很失望)。
    【解决方案3】:

    使用

    slot_t slots[] = (slot_t[])calloc(n,sizeof(slot_t))
    

    它本身就是干净的内存

    【讨论】:

    • calloc 用 0 填充返回的块,所以如果这是需要的 - 这应该足够了。
    • 不要从malloc() 转换返回值。它可以隐藏错误并且不会为您提供任何价值,除非您打算移植到 C++。
    • 这实际上并没有编译(malloc 的原始行也没有)。
    • 是的,OP 需要*,这个答案也需要。
    • @Carl:我不认为你可以用指针初始化一个数组。 Clang 说:“错误:数组初始化程序必须是初始化程序列表”。
    【解决方案4】:

    memset 用字节填充内存。见here

    如果您想要一个通用 解决方案 - 您应该编写一个循环来迭代和填充。如果你用 0 填充,那么它是什么类型的数据以及它的大小都没有关系 - 只需用整个数组的 siezof (sizeof(slot_t)*n) 填充 0。由于您使用的是NULL,它不一定是 0(尽管通常是) - 我建议采用更安全的“循环”方法。

    【讨论】:

      【解决方案5】:

      如果您想要一个完全通用的函数,它将对象数组设置为某个“模板”对象指定的值,您可以使用如下函数:

      void init_array( void* arr, size_t nmemb, size_t size, void const* initializer)
      {
          size_t i = 0;
      
          char* p = (char *) arr;
      
          for (i = 0; i < nmemb; ++i) {
              memcpy( p, initializer, size);
              p += size;
          }
      }
      

      那么您的分配/初始化代码可能如下所示:

      typedef void* slot_t;
      
      static const slot_t empty_slot = NULL;    // or make this a global if that 
                                                //  works better for your scenario
      
      int n = 10;
      
      // note: your original `malloc()` line:
      //
      //      slot_t slots[] = (slot_t[])malloc(sizeof(slot_t)*n)
      //
      // wouldn't work, as you can't assign to an array as a whole.
      // That line shouldn't even compile.
      
      slot_t* slots = (slot_t*)malloc(sizeof(slot_t)*n);
      
      // completely generic initialization
      init_array( slots, n, sizeof(slot_t), &empty_slot);
      

      如果你想初始化一个指针数组,你可以有另一个函数更直接地处理这种情况:

      void init_ptr_array( void* arr, size_t nmemb, void* initializer)
      {
          size_t i = 0;
          void* p;
      
          for (; p < arr + nmemb; ++p) {
              *p = initializer;
          }
      }
      
      // arrays of object pointers
      init_ptr_array( slots, n, empty_slot);
      

      我不确定我是否喜欢这两个函数对最后一个参数的含义略有不同。如果我的程序中需要这两种初始化,我可能会坚持使用通用的初始化指针数组。它的效率可能有点低,但初始化通常不是瓶颈。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-06
        • 1970-01-01
        • 2011-09-11
        • 2020-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多