我正在研究一系列 C 函式以允許用戶動態構建陣列。該庫的核心位于Array struct其中包含一個包含陣列資料的指標變數array,len其中包含陣列的長度size,它是陣列的總記憶體分配elem,它包含每個索引的記憶體分配,以及指標變數name其中dtype包含描述陣列名稱和陣列型別的字串。目前我已經限制了范圍,因此只能考慮int、float、double和char陣列。
到目前為止,我已經定義并單獨測驗了以下功能;
array_mem_alloc其中包含為陣列分配記憶體的代碼。init_array這是一個包裝器,array_mem_alloc它實體化一個Arraystruct,確定資料型別并將資料型別回傳Array給用戶。append_array它允許用戶一次動態地增長一個陣列,或者添加一個已經定義的陣列。free_array釋放所有記憶體并重置struct變數int_array_val它在索引處對資料進行型別轉換并回傳給用戶。我有所有相關資料型別的這個函式的版本,但是對于這個問題我只會使用這個版本。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函式,一切正常,它還回傳另一個Array名為p. 但是,當我嘗試使用該int_array_val函式檢索整數變數時,它失敗了,因為它無法將變數識別array->dtype為包含 string "int"。但是,當我在 main 函式內部和內部測驗容器時find_int_array_indices,變數確實包含 string "int"。這告訴我我可能有一個指標錯誤,但我沒有看到它。任何建議都會非常有用。我想知道我是否需要回到開始并定義name并dtype作為固定長度陣列Array struct而不是指標變數。
陣列.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);
陣列.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;
}
主程式
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);
uj5u.com熱心網友回復:
在find_int_array_indices
char dtype[7] = "int";
char name[9] = "indices";
都是區域變數,在函式回傳時失效。請參閱:懸空指標和Lifetime。
init_array使用這些值,就好像它們有一個生命周期來匹配它的回傳值
Array array;
array.dtype = dtype;
array.elem = size;
array_mem_alloc(&array, num_indices);
array.name = name;
return array;
作為結構型別,它的生命周期由呼叫者的背景關系決定(畢竟回傳是副本)。
find_int_array_indicesindice_arr回傳時完成錯誤main。
一些選項:
- 嚴格使用指向具有靜態存盤持續時間的字串的指標。
- 更改您的結構定義以包含這些字串的空間(或分配它),并執行字串復制。
- 請改用列舉型別。
- 通過一般支持所有記憶體大小來拋棄這種基于字串的、型別受限的范例(盡管命名功能仍然是一個問題)。
一個相當冗長的延續,詳細說明使用列舉型別:
這個想法是定義一組較小的可接受值,您的庫可以使用這些值,并讓用戶更加了解這些值。正如我們所看到的,您已經部分使用字串完成了這項作業,但實作存在一些問題,因為字串通常很笨重。字串的一些問題:
- 您無法控制庫用戶使用的字串(這會導致您必須退出1以防用戶輸入意外的內容,這很容易做到),
- 您必須考慮它們潛在的大量或過多的記憶體消耗,
- 字串比較是 O(N),
- 字串在 C 中通常是不安全的,在處理它們(賦值、比較、存盤)時需要比其他基本結構更加小心。
因此,我們不使用字串(在這些示例中為"foo", "bar" "qux"),而是使用列舉型別
enum OBJECT_TYPE {
OBJECT_FOO,
OBJECT_BAR,
OBJECT_QUX
};
它確立了以下內容:
- 更清楚的是可接受的值是什么
- 一些2通過型別提示控制用戶輸入的內容
- 比較是 O(1)
- 處理與任何整數型別相同
The structure definition then looks like
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;
Nothing can really be done about the name member of your structure. If you want user defined nametags for things, then yes, as stated previously, you need to allocate space for them.
Our initialization function works similarly, but we can2 take advantage of some properties of integral types.
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 = '\0';
}
Now we want to provide a function that works with our generic data of only type OBJECT_FOO (like your int_array_val). Again, the comparison is much easier to understand.
void object_print_foo(Object *o) {
if (OBJECT_FOO != o->type)
/* handle type mismatch */;
}
Although it would be better to provide a generic object_print function that again branches based on o->type.
A main function for completeness:
int main(void) {
Object a;
object_init(&a, OBJECT_QUX, "object_a");
object_print_foo(&a);
}
This is the general idea of using enumerated types.
With all that said, I think this is not really any better than just handling arbitrary data sizes, risks included. Something like
const void *array_get(Array *array, size_t index) {
if (index >= array->length)
return NULL;
return (char *) array->array index * array->elem;
}
works, if the user respects the const contract, and uses the correct types (they would need to remember their typing with specifically typed getters too).
Generic data structures in C are a bit of a leap of faith no matter what.
1. So a note on exiting from library code: don't. As a library author, you have no reasonable right to cause user programs to terminate (unless requested, or the user invokes UB outside your control). Delegate upwards, return errors, and let the user exit the program on their own terms, as they may need to perform their own cleanups (or might carry on if the failure is non-critical).
2. C's enumeration type is rather weak. enum are actually just int, and users can enter plain integer values outside the specified ranges. This is akin to invoking undefined behavior from a library's point of view, but we may wish to protect the user anyway.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/456698.html
