Как завершить массив переменной длины, когда все значения действительны? - PullRequest
0 голосов
/ 23 сентября 2019

Я передаю массив значений с плавающей запятой одинарной точности функции в C. Функция не знает о размере массива, и я хотел бы сохранить его таким образом, в первую очередь потому, что хотя базовый массивконечно, фиксированной длины, я не всегда буду заполнять ее полностью, поэтому мне все равно нужно будет найти конец.Со строкой вы используете нулевой терминатор, но в этой реализации все возможные значения потенциально допустимы.Лучшее, что я могу сделать, как «кодовое слово», чтобы отметить конец, используя несколько значений в порядке, что-то вроде ASCII «STOP»?Это оставляет открытой возможность совпадения этого кодового слова в массиве допустимых данных ...

Ответы [ 4 ]

4 голосов
/ 23 сентября 2019

Вы увидите, как пары массив / размер передаются в C a lot , это действительно единственный способ сделать это надежно.Даже строки C, которые заканчиваются NUL, часто отправляются с параметром длины, чтобы быть уверенным, что вы случайно не ушли с конца массива в другую память.

Этот подход также позволяет вам использовать подстрокиили подмножества массива, вместо того, чтобы использовать все целиком, проблему, которую вы в основном пытаетесь решить.Наличие терминатора является и благословением, и проклятием, что может подтвердить любой, кто когда-либо пытался бороться с пагубной ошибкой переполнения буфера.

В вашем случае сигнатура функции должна выглядеть следующим образом:

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);
2 голосов
/ 23 сентября 2019

Вы можете использовать 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) {
    ...
2 голосов
/ 23 сентября 2019

Вам не нужно передавать максимальную длину массива, только длину, которая в данный момент используется для этого вызова вместе с указателем.

1 голос
/ 23 сентября 2019

В строке используется нулевой терминатор, но в этой реализации все возможные значения потенциально допустимы.

Если все значения действительны, значение дозорного не может быть реализовано.Это так просто (вот почему EOF является целочисленным значением, которое переполняет тип char).

Функция не знает о размере массива, и я хотел бычтобы сохранить его таким образом ...

Предполагая, что NaN является недопустимым значением, вы можете использовать макрос isnan() для проверкизначение часового.

Однако, NaN является допустимым значением ...

В любом случае мне нужно будет найти конец.

Единственный оставленный вариант - фактически передать длину массива вместе с массивом.

Если вы не можете добавить длину массива в качестве отдельного аргумента, вы можете (вероятно) сохранить длину массивав качестве первого члена - либо с использованием структуры (рекомендуется), либо с использованием типа punning (не пытайтесь сделать это дома, если вы не знаете, что делаете).

то есть

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]; }

На самом деле нет смысла использовать циклы вычислений, если вы можете просто передать длину действительной длины массива вместе с массивом.

Edit (type punning)

Я настоятельно рекомендую избегать такого подхода, так как длины типов могут привести к трудностям при обнаружении ошибок.Однако ...

Возможно сохранить длину массива в первом float элементе, используя те же байты (память) для хранения целого числа.

Обратите внимание, что это можетсбой (или, что еще хуже, тихий сбой), если unsigned int длиннее float (что может быть, даже если они обычно имеют одинаковый размер в байтах).

т.е.

#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);
}

PS

Я надеюсь, что вы будете придерживаться простого func(float * arr, size_t len), теперь, когда вы видите, сколько дополнительного кода вам нужно, просто чтобы избежать передачи длины массива.

...