[英]Is it UB to cast a pointer to a void pointer and write to it?
我正在研究一種在 C 中創建動態數組的方法,我想出了這個解決方案作為我希望我的函數/宏如何工作的一般結構:
//dynarray.h
#define dynarray(TYPE)\
struct{\
TYPE *data;\
size_t size;\
size_t capacity;\
}
int dynarray_init_internal(void **ptr, size_t *size, size_t *cap, size_t type_size, size_t count);
#define dynarray_init(ARR, SIZE) dynarray_init_internal(&ARR->data, &ARR->size, &ARR->capacity, sizeof(*ARR->data), SIZE)
//dynarray.c
int dynarray_init_internal(void **ptr, size_t *size, size_t *cap, size_t type_size, size_t count){
*ptr = malloc(type_size*count);
if(*ptr == NULL){
return 1;
}
*size = 0;
*cap = count;
return 1;
}
這是具有通用函數/宏組合以類型不可知的方式處理動態分配內存的可接受方法嗎?
我對此唯一的懷疑是我不確定這是否是未定義的行為。 我想這可以很容易地擴展到動態數組結構通常需要的其他功能。 我能看到的唯一問題是,由於它是一個匿名結構,因此您無法將其作為參數傳遞到任何地方(至少很容易),但是可以通過創建一個dynarray_def(TYPE, NAME)
宏來輕松解決這個dynarray_def(TYPE, NAME)
該宏將定義一個帶有NAME
的動態數組結構,讓它保存TYPE
數據,同時仍然可以與上面列出的所有其他函數/宏樣式一起使用。
這是未定義的行為,因為您正在將(例如) int **
轉換為void **
並取消引用它以產生void *
。 與void *
的自動轉換不會擴展到void **
。 將一種類型讀/寫為另一種類型(在這種情況下,將int *
寫為void *
)是違規的。
處理這個問題的最好方法是使整個 init 例程成為一個宏:
#define dynarray_init(ARR, SIZE) \
do {\
(ARR)->data = malloc(sizeof(*(ARR)->data*(SIZE));\
if ((ARR)->data == NULL){\
_exit(1);\
}\
(ARR)->size = 0;\
(ARR)->capacity = (SIZE);\
} while (0)
編輯:
如果您希望避免使用類似函數的宏,您可以改為使用宏來創建一個函數及其使用的結構類型:
#include <stdio.h>
#include <stdlib.h>
#define dynarray(TYPE)\
struct dynarray_##TYPE {\
TYPE *data;\
size_t size;\
size_t capacity;\
};\
\
int dynarray_##TYPE##_init(struct dynarray_##TYPE **ptr, size_t count){\
*ptr = malloc(sizeof(*ptr)*count);\
if(*ptr == NULL){\
return 1;\
}\
\
(*ptr)->size = 0;\
(*ptr)->capacity = count;\
return 1;\
}
// generate types and functions
dynarray(int)
dynarray(double)
int main()
{
struct dynarray_int *da1;
dynarray_int_init(&da1, 5);
// use da1
struct dynarray_double *da2;
dynarray_double_init(&da2, 5);
// use da2
return 0;
}
因為一些罕見的實現對不同類型的指針使用不同的表示,所以標准不要求實現允許它們互換操作。 相反,它將對此類操縱的支持視為“流行擴展”,對其的支持是其管轄范圍之外的“實施質量”問題。 幾乎任何用於遠程普通平台的編譯器都可以進行配置以支持該構造,雖然標准的作者希望給程序員一個“戰斗機會”[他們的話] 來編寫可移植的代碼,但他們已經明確表示他們做到了不希望“貶低”不是 100% 可移植的程序。
但是請注意,某些優化器無法處理此類構造,除非完全禁用基於類型的別名分析被禁用,並且任何使用此類構造的程序都需要記錄此類要求。 另一方面,除非需要針對晦澀的架構,否則最好使用構造並記錄其使用情況通常比跳過箍以適應劣質優化器要好。
請注意,順便說一句,即使是高質量的編譯器也可能會被一些涉及指針強制轉換的足夠棘手的使用模式絆倒。 標准的作者不想僅僅因為一些棘手和人為的使用模式可能會產生不正確的行為就禁止實現執行有用的優化,但他們希望實現能夠識別用戶實際使用的模式。 例如,給定:
float f;
int *ip; float *fp;
int *ipp = (int**)(&fp);
...
void test(void)
{
fp = &f;
f = 1.0;
**ip+=1;
return f;
}
編譯器沒有現實的方法來識別對**ip
的寫入可能會實際影響float
類型的對象。 但是,如果fp
的地址在寫入f
和稍后讀取之間已經存儲到ip
,那么在編寫標准時代的優化編譯器會認識到將T*
轉換為U*
應該被視為可能通過U*
訪問的T*
類型的任何對象上的潛在內存破壞。 我懷疑您的使用模式比前者更適合后一種模式。
*ipp = someFloat;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.