将数据分配给 C 中的 typedef 结构时出现指针错误

Pointer error when assigning data to a typedef struct in C

我正在研究一系列 C 函数,以允许用户动态构建数组。库的核心驻留在Array struct其中包含一个指针变量array包含数组数据,len包含数组的长度,size,这是数组的总内存分配,elem,其中包含每个索引的内存分配,以及指针变量 namedtype,其中包含描述数组名称的字符串和数组的类型。目前我已经限制了范围,因此只能考虑 intfloatdoublechar 数组。

到目前为止,我已经定义并单独测试了以下功能;

  1. array_mem_alloc 其中包含为数组分配内存的代码。

  2. init_arrayarray_mem_alloc 的包装器,实例化 Array struct,确定数据类型,returns 一个Array 用户的数据类型。

  3. append_array 允许用户一次动态增长数组一个索引,或者添加一个已经定义的数组。

  4. free_array 释放所有内存并重置 struct 变量

  5. int_array_val 将索引处的数据和 returns 类型转换为用户。我有适用于所有相关数据类型的此函数的版本,但对于这个问题我只会使用此版本。

  6. find_int_array_indices 查找数组中存在特定整数的位置,并将索引号记录到返回给用户的另一个数组中。

出于测试 find_int_array_indices 的目的,我正在调用 init_array 一个名为 arr_test 的变量,并在其后附加 7 个整数 int a[7] = {6, 1, 3, 6, 6, 4, 5}。我将 Array 容器 arr_test 传递给 find_int_array_indices 函数,一切正常,这也是 returns 另一个名为 pArray 容器。但是,当我尝试使用 int_array_val 函数检索整数变量时,它失败了,因为它无法将变量 array->dtype 识别为包含字符串 "int"。但是,当我在 find_int_array_indices 和主函数中测试容器时,变量确实包含字符串 "int"。这告诉我,我可能有一个指针错误,但我没有看到它。任何建议都会非常有用。我想知道我是否需要回到开头并将 namedtype 定义为 Array struct 中的固定长度数组而不是指针变量。

array.h

typedef struct
{
    void *array;  // Pointer to array
    size_t len;   // Active length of array
    size_t size;  // Number of allocated indizes
    int elem;     // Memory consumption per indice
    char *name;   // The array name
    char *dtype;  // A string representing the datatype
} Array;

void array_mem_alloc(Array *array, size_t num_indices);
Array init_array(char *dtype, size_t num_indices, char *name);
int append_array(Array *array, void *elements, size_t count);
void free_array(Array *array);
int int_array_val(Array *array, int indice);
Array find_int_array_indices(Array *array, int integer);

array.c

void array_mem_alloc(Array *array, size_t num_indices) {
    // Determine the total memory allocation and assign to pointer
    void *pointer;
    pointer = malloc(num_indices * array->elem);

    // If memory is full fail gracefully
    if (pointer == NULL) {
        printf("Unable to allocate memory, exiting.\n");
        free(pointer);
        exit(0);
    }
    // Allocate resources and instantiate Array
    else {
        array->array = pointer;
        array->len = 0;
        array->size = num_indices;
    }
}
// --------------------------------------------------------------------------------

Array init_array(char *dtype, size_t num_indices, char *name) {
    // Determine memory blocks based on data type
    int size;
    if (strcmp(dtype, "float") == 0) size = sizeof(float);
    else if (strcmp(dtype, "int") == 0) size = sizeof(int);
    else if (strcmp(dtype, "double") == 0) size = sizeof(double);
    else if (strcmp(dtype, "char") == 0) size = sizeof(char);
    else {
        printf("Data type not correctly entered into init_array, exiting program!\n");
        exit(0);
    }

    // Allocate indice size and call array_mem_alloc
    Array array;
    array.dtype = dtype;
    array.elem = size;
    array_mem_alloc(&array, num_indices);
    array.name = name;
    return array;
}
// --------------------------------------------------------------------------------

int append_array(Array *array, void *elements, size_t count) {
    // Allocae more memory if necessary
    if (array->len + count > array->size) {
        size_t size = (array->len + count) * 2;
        void *pointer = realloc(array->array, size * array->elem);
        // If memory is full return operations
        if (pointer == NULL) {
            printf("Unable to allocate memory, exiting.\n");
            return 0;
        }
        // Allocate memory to variables and increment array size
        array->array = pointer;
        array->size = size;
    }
    // Append variables and increment the array length
    memcpy((char *)array->array + array->len * array->elem, elements, count * array->elem);
    array->len += count;
    return 1;
}
// --------------------------------------------------------------------------------

void free_array(Array *array) {
    // Free all memory in the array
    free(array->array);
    // Reset all variables in the struct
    array->array = NULL;
    array->size = 0;
    array->len = 0;
    array->elem = 0;
}
// --------------------------------------------------------------------------------

int int_array_val(Array *array, int indice) {
    // Ensure array contains integers
    printf("%s\n", array->dtype);
    if (strcmp(array->dtype, "int") != 0) {
        printf("Function can only return integer values, exiting function!\n");
        exit(0);
    }
    // Cast value to an integer and return
    int a = ((int *)array->array)[indice];
    return a;
}

Array find_int_array_indices(Array *array, int integer) {
    int number = 0;
    int input;
    for (int i = 0; i < array->len; i++) {
        if (integer == int_array_val(array, i)) {
            number++;
        }
    }
    char dtype[7] = "int";
    char name[9] = "indices";
    
    Array indice_arr = init_array(dtype, number, name);
    for (int i = 0; i < array->len; i++) {
        input = i;
        if (integer == int_array_val(array, i)) {
            append_array(&indice_arr, &input, 1);
        }
    }
    return indice_arr;
}

main.c

size_t indices = 10;
char name[6] = "array";
char dtype[7] = "int";
Array arr_test = init_array(dtype, indices, name);
int a[7] = {6, 1, 3, 6, 6, 4, 5};
append_array(&arr_test, a, 7);
Array p = find_int_array_indices(&arr_test, 6);
printf("%s\n", p.dtype); // This shows that p does contain dtype "int"
int d = int_array_val(&p, 0); // This fails in function, because it does not see dtype = "int"???
printf("%d\n", d);

find_int_array_indices

char dtype[7] = "int";
char name[9] = "indices";

都是局部变量,在函数return时失效。参见:Dangling pointer and Lifetime.

init_array 使用这些值就好像它们有生命周期来匹配它的 return 值

Array array;
array.dtype = dtype;
array.elem = size;
array_mem_alloc(&array, num_indices);
array.name = name;
return array;

作为结构类型,其生命周期由其调用者的上下文决定(return 毕竟是复制)。

find_int_array_indices在returnsindice_arrmain.

时完成错误

部分选项:

  • 严格使用指向带 static storage duration 的字符串的指针。
  • 更改您的结构定义以包含这些字符串的 space(或分配它),并执行字符串复制。
  • 改用 enumerated type
  • 放弃这个 string-based,通过通用地支持所有内存大小(尽管命名功能仍然是一个问题)将类型受限的范例放在一起。

一个相当long-winded的延续,详细说明如何使用enumerated types:

我们的想法是定义一组较小的图书馆可以使用的可接受值,并让用户更加了解这些值。正如我们所看到的,您已经使用字符串部分完成了该操作,但实现存在一些问题,因为字符串通常很笨重。字符串的一些问题:

  • 无法控制您图书馆的用户使用的字符串(这导致您必须退出1程序用户输入意外的事件,这很容易做到),
  • 您必须考虑到它们潜在的大量或过多的内存消耗,
  • 字符串比较复杂度为O(N),
  • 字符串在 C 中通常是不安全的,在处理它们(赋值、比较、存储)时需要比其他基本结构更加小心。

因此,我们不使用字符串(在这些示例中为 "foo""bar”、"qux"),而是使用枚举类型

enum OBJECT_TYPE {                  
    OBJECT_FOO,                        
    OBJECT_BAR,                        
    OBJECT_QUX 
};

它建立了以下内容:

  • 更清楚可接受的值是什么
  • 一些2 通过类型提示控制用户输入的内容
  • 比较是O(1)
  • 处理与任何整数类型相同

结构定义看起来像

typedef struct {                       
    /* ... whatever members are needed for the structure */ 
                                                               
    size_t something_based_on_type;
    
    enum OBJECT_TYPE type;   
    char debug_name[MAX_DEBUG_NAME];                       
} Object;

对于结构的名称成员,真的无能为力。如果您想要用户定义的名称标签,那么是的,如前所述,您需要为它们分配 space。

我们的初始化函数的工作方式类似,但我们可以2利用整数类型的一些属性。

void object_init(Object *object, enum OBJECT_TYPE type, const char *debug_name) {
    /*  ... accept other arguments, whatever is needed to initialize */

    size_t value_translations[] = { 42, 51, 99 };

    object->type = type;
    /* while neat, this is somewhat naive, see footnotes */
    object->something_based_on_type = value_translations[type];

    if (debug_name && strlen(debug_name) < MAX_DEBUG_NAME)
        strcpy(object->debug_name, debug_name);
    else
        *object->debug_name = '[=14=]';
}

现在我们想提供一个函数,它只处理我们类型为 OBJECT_FOO 的通用数据(比如您的 int_array_val)。同样,比较更容易理解。

void object_print_foo(Object *o) {
    if (OBJECT_FOO != o->type)
        /* handle type mismatch */;
}

虽然最好提供一个通用的 object_print 函数,它再次基于 o->type 进行分支。

完整性的主要功能:

int main(void) {
    Object a;

    object_init(&a, OBJECT_QUX, "object_a");
    object_print_foo(&a);
}

这是使用枚举类型的一般思路。

综上所述,我认为这并不比处理任意数据大小好多少,包括风险。像

const void *array_get(Array *array, size_t index) {
    if (index >= array->length)
        return NULL;
    
    return (char *) array->array + index * array->elem;
}

如果用户尊重 const 合同并使用正确的类型(他们也需要记住他们使用特定类型的 getter 的类型),则可以工作。

无论如何,C 中的通用数据结构都是一种信仰的飞跃。


1. 所以关于 exiting 来自图书馆代码的注释:不要。作为库作者,您没有合理的权利导致用户程序终止(除非请求,或者用户在您的控制之外调用 UB)。向上委托,return 错误,并让用户按自己的方式退出程序,因为他们可能需要执行自己的清理(或者如果失败是 non-critical,则可能会继续)。

2. C的枚举类型比较弱。 enum 实际上就是 int,用户 可以 输入超出指定范围的普通整数值。这类似于从图书馆的角度调用 undefined behavior,但无论如何我们可能希望保护用户。