【问题标题】:How to terminate variable length array when all values are valid?当所有值都有效时如何终止可变长度数组?
【发布时间】:2020-01-23 20:38:16
【问题描述】:

我将一个单精度浮点值数组传递给 C 中的一个函数。该函数不知道数组的大小,我想保持这种方式,主要是因为底层数组当然是固定长度的,我不会总是完全填满它,所以无论如何我都需要能够找到结尾。对于字符串,您使用空终止符,但在此实现中,所有可能的值都可能有效。我能做的最好的事情是像“代码字”那样按顺序使用多个值来标记结尾,比如 ASCII 'STOP'?这就留下了在有效数据数组中巧合地拥有该代码字的可能性......

【问题讨论】:

  • 我们通常会传递长度。
  • 1. NaN 是有效值吗? 2. 为什么不将长度作为数组本身的第一个值添加?
  • 仅供参考,标记数组或其他序列结束的值称为 sentinel。如果元素类型的任何值可能出现在所需数据中,那么您就不能拥有“带内”标记,因此您必须使用不同的数据类型(例如一个字段指示它是否是数据元素的结构或哨兵,或具有更多值的更大类型,因此您可以将其用作哨兵)或以某种方式指示函数的长度。
  • @tadman - 同意,这就是为什么我赞成你的回答:)
  • @tadman - 我宁愿不要将经验不足的开发人员推向更深的水域(例如union / 类型双关语方法)......这个问题显然标志着 OP 是一个经验不足的开发人员(暂时)。

标签: c arrays variable-length-array null-terminated


【解决方案1】:

您不需要传递数组的最大长度,只需传递当前用于此调用的长度以及指针即可。

【讨论】:

    【解决方案2】:

    您将看到数组/大小对在 C 中传递一个 lot,这确实是可靠地做到这一点的唯一方法。即使是 NUL 终止的 C 字符串,也经常带有长度参数,以确保您不会无意中离开数组的末尾并进入其他内存。

    这种方法还允许您使用子字符串或数组的子集,而不是致力于使用整个事物,即您基本上试图解决的问题。拥有终结者既是福也是祸,任何曾尝试与有害的缓冲区溢出错误作斗争的人都可以证明这一点。

    在您的情况下,函数签名应如下所示:

    void process(float* v, size_t n)
    

    其中v 是要处理的浮点值数组,n 是要使用的浮点值的数量。 n 应小于或等于 v 数组中的许多有效条目。

    如果你经常传递这种东西,你甚至可以将它封装在一个定义数据和大小的简单结构中。然后,您可以使用一些简单的分配器/填充器工具。

    例如:

    struct float_array {
      float* values;
      size_t size;
    };
    

    然后您可以在其中定义如下内容:

    struct float_array* make_float_array(size_t n);
    void free_float_array(struct float_array* f);
    

    【讨论】:

      【解决方案3】:

      您可以通过这种方式使用 NAN,假设这不是您的数据集的有效值:

      #include <math.h>
      
      float average(float *array)
      {
          float sum = 0.0; // Declare this as double for better precision
          size_t index = 0;
      
          // x == NAN will return false for all x including NAN, so we need
          // the function isnan()
          while(! isnan(array[index])) 
              sum += array[index++];
          return sum/index;
      }
      

      由于您可能希望对许多函数执行此操作,因此我建议您编写一个计算长度的函数:

      size_t farray_length(float *array)
      {
          size_t len = 0;
          while(! isnan(array[len])) len++;
          return len;
      }
      

      但在 C 中解决这些问题的常用方法是将大小作为单独的参数发送。

      float average(float *array, size_t size) 
      {
          float sum = 0.0;
          for(size_t i=0; i<size; i++)
              sum += array[i];
          return sum/size;
      }
      

      第三种方法(例如,如果您使用不希望用户直接弄乱的对象编写库)是有用的,它是声明一个结构。

      struct float_array {
          float *array;
          size_t size;
      }
      
      float average(float_array array) {
          ...
      

      【讨论】:

      • +1 我想过一个类似的解决方案,而将长度与数组一起传递似乎是一个更合乎逻辑的解决方案
      【解决方案4】:

      对于字符串,您使用空终止符,但对于此实现,所有可能的值都可能有效。

      如果所有值都有效,则无法实现标记值。就这么简单(这就是为什么EOF 是一个溢出char 类型的整数值)。

      该函数不知道数组的大小,我想保持这种状态...

      假设NaN 是无效值,您可以使用isnan() macro 来测试标记值。

      但是,NaN 是一个有效值...

      无论如何我都需要能够找到结局。

      剩下的唯一选择是实际将数组长度与数组一起传递。

      如果您不能将数组长度添加为单独的参数,您可以(可能)将数组的长度存储为第一个成员 - 使用结构(推荐)或使用类型双关语(不要尝试这个除非你知道自己在做什么)。

      typedef struct float_array_s {
        unsigned int len;
        float f[];
      };
      
      static unsigned int float_array_len(float_array_s * arr) { return arr->len; }
      static float float_array_index(float_array_s * arr, unsigned int index) { return arr->f[index]; }
      

      如果您可以简单地将有效数组长度的长度与数组一起传递,那么真的没有理由使用计算周期。

      编辑(双关语)

      我强烈建议避免使用这种方法,因为类型长度可能会导致难以检测到错误。不过……

      可以将数组的长度存储在第一个 float 成员中,方法是使用相同的字节(内存)来存储整数。

      请注意,如果 unsigned intfloat 长(可能是这样,即使它们通常具有相同的字节大小),这可能会崩溃(或者最糟糕的是,静默失败)。

      #include "math.h"
      #include "stdint.h"
      #include "stdio.h"
      
      /* Returns the member at `index`. */
      static float float_array_index_get(float *arr, unsigned int index) {
        return arr[index + 1];
      }
      /* Sets the member at `index` to `val. */
      static void float_array_index_set(float *arr, unsigned int index, float val) {
        arr[index + 1] = val;
      }
      /* Returns the array's length. */
      static unsigned int float_array_length_get(float *arr) {
        if (sizeof(unsigned int) > sizeof(float)) {
          fprintf(
              stderr,
              "ERROR: (%s:%d) type size overflow, code won't work on this system\n",
              __FILE__, __LINE__);
        }
        union {
          float f;
          unsigned int i;
        } pn;
        pn.f = arr[0];
        return pn.i;
      }
      /* Sets the array's length. */
      static void float_array_length_set(float *arr, unsigned int len) {
        if (sizeof(unsigned int) > sizeof(float)) {
          fprintf(
              stderr,
              "ERROR: (%s:%d) type size overflow, code won't work on this system\n",
              __FILE__, __LINE__);
        }
        union {
          float f;
          unsigned int i;
        } pn;
        pn.i = len;
        arr[0] = pn.f;
      }
      /* Pushes a member to the array, increasing it's length. */
      static void float_array_index_push(float *arr, float val) {
        unsigned int len = float_array_length_get(arr);
        float_array_index_set(arr, len, val);
        float_array_length_set(arr, len + 1);
      }
      /* Pops a member from the array...
       * ... returning nan if the member was nan or if the array is empty.
       */
      static float float_array_index_pop(float *arr) {
        unsigned int len = float_array_length_get(arr);
        if (!len)
          return nan("");
        float_array_length_set(arr, len);
        return float_array_index_get(arr, len);
      }
      

      附言

      我希望你会坚持使用简单的func(float * arr, size_t len),因为你已经看到了需要多少额外的代码才能避免传递数组的长度。

      【讨论】:

      • 另一种未提及的可能性:NaN 有多种位模式。您可以选择使用一种特定的表示来表示“NaN”,并使用不同的表示来表示哨兵。
      猜你喜欢
      • 1970-01-01
      • 2020-09-16
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-19
      • 2014-03-27
      相关资源
      最近更新 更多