[英]Using void pointers in generic accessor function with ANSI C
我有一段C代碼的工作示例,我正在用它來教自己如何在非平凡的應用程序中有效地使用指針。 (我有一個夢想,就是為我所依賴的C庫提供缺少的功能。)
我的示例代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
struct config_struct {
int port;
char *hostname;
};
typedef struct config_struct config;
void setup(config*);
void change(config*);
void set_hostname(config*, char*);
void get_hostname_into(config*, char**);
void teardown(config*);
void inspect(config*);
int main() {
char* hostname;
config* c;
c = calloc( 1, sizeof(config));
setup(c);
inspect(c);
change(c);
inspect(c);
set_hostname(c, "test.com");
inspect(c);
get_hostname_into(c, &hostname);
inspect(c);
printf("retrieved hostname is %s (%p)\n", hostname, &hostname);
teardown(c);
printf("retrieved hostname is %s (%p) (after teardown)\n", hostname, &hostname);
return EXIT_SUCCESS;
}
void setup(config* c) {
c->port = 9933;
c->hostname = "localhost";
}
void change(config* c) {
c->port = 12345;
c->hostname = "example.com";
}
void set_hostname(config* c, char* new_hostname) {
c->hostname = new_hostname;
}
void get_hostname_into(config* c, char** where) {
*where = c->hostname;
}
void teardown(config* c) {
free(c);
}
void inspect(config* c) {
printf("c is at %p\n", c);
printf("c is %ld bytes\n", sizeof(*c));
printf("c:port is %d (%p)\n", c->port, &(c->port));
printf("c:hostname is %s (%p)\n", c->hostname, &(c->port));
}
庫的性質是get_session_property(session*, enum Property, void*)
函數是get_session_property(session*, enum Property, void*)
-因此,我正在尋找取消引用void指針的方法;我能夠為int
成功實現此功能,但是一直在踢我的腳跟試圖找出如何做到這一點的一個char*
(約一些void*
以int
做一些感覺,但我無法捉摸如何做到這一點的void* to char*
。
我對該庫的成功實現(帶有測試)在我項目的Github分支上, 在此處 。
我最接近的是:
enum Property { Port, Hostname };
void get_property(config*, enum Property, void*);
void get_property(config* c, enum Property p, void* target) {
switch(p) {
case Port:
{
int *port;
port = (int *) target;
*port = c->port;
}
break;
case Hostname:
{
char *hostname;
hostname = (char *) target;
*hostname = c->hostname;
}
break;
}
}
哪個不會有段錯誤,還會使char *get_hostname_into_here
null
,發出警告(我不知道:)
untitled: In function ‘get_property’:
untitled:33: warning: assignment makes integer from pointer without a cast
我在這里做的例子的完整源代碼; 請回答時解釋一下,或者推薦您使用空指針和/或良好的C風格的任何讀物,似乎每個人都有不同的想法,而我在現實世界中認識的幾個人只是說:“圖書館正在這樣做錯誤,請不要使用void指針)-雖然該庫將公開該結構會很好;但出於封裝和其他良好原因,我認為在這種情況下,void指針,泛型函數方法是完全合理的。
因此,我在get_property()
函數的hostname
分支中做錯了什么,即在調用get_property(c, Hostname, &get_hostname_into_here);
之后, char*
為NULL
get_property(c, Hostname, &get_hostname_into_here);
char *get_hostname_into_here;
get_property(c, Hostname, &get_hostname_into_here);
printf("genericly retrieved hostname is %s (%p)\n", get_hostname_into_here, &get_hostname_into_here);
// Expect get_hostname_into_here not to be NULL, but it is.
應該更改get_property
函數,以便target
是一個double void指針,這意味着您可以更改指針本身(不僅是它所引用的內存):
void get_property (config *c, enum Property p, void **target) {
switch (p) {
case Port:
*((int *) (*target)) = c->port;
break;
case Hostname:
*target = c->config;
break;
}
}
然后使用類似的功能:
int port;
int *pport = &port;
char *hostname;
get_propery(c, Port, &pport);
get_propery(c, Hostname, &hostname);
就像我在上面的評論中說的那樣,不可能給出確切的答案,因為目前尚不清楚目標是什么。 但我看到兩種可能性:
1: target
指向char
緩沖區
如果是這種情況,那么您似乎需要將字符串的內容復制到該緩沖區中。 這不可能安全地進行,因為您不知道接收緩沖區的大小。 但是,如果您對此不關心,則需要執行以下操作:
strcpy((char *)target, c->hostname);
2: target
指向一個char *
如果是這種情況,則可能是要修改該char *
以指向現有字符串,或者動態創建一個新的緩沖區,復制該字符串,然后修改char *
以指向它。
所以:
char **p = (char **)target;
*p = c->hostname;
要么:
char **p = (char **)target;
*p = malloc(strlen(c->hostname)+1);
strcpy(p, c->hostname);
注意
您收到警告消息,因為在此行中:
*hostname = c->hostname;
*hostname
類型為char
,而c->hostname
類型為char *
。 編譯器告訴您此轉換沒有任何意義。 如果我是您,我將設置您的編譯器將警告視為錯誤(例如,使用-Werror
GCC標志),因為應該始終遵守警告!
在您提供有關get_property
函數的更多詳細信息之前,您的問題沒有答案。 顯然, void *target
參數用於傳遞外部“空格”,您應在該空格中放置結果-所請求屬性的值。
該接收者空間的本質是什么?
如果是int
屬性,則代碼很清楚:指針指向應該在其中放置屬性值的一些int
對象。 這是您正確執行的操作。
但是字符串屬性呢? 這里至少有兩種可能性
1) void *target
參數指向char []
緩沖區的開頭,該緩沖區應該足夠大以接收任何屬性值。 在這種情況下,您的代碼應如下所示
case Hostname:
{
char *hostname = target;
strcpy(hostname, c->hostname);
}
break;
這種情況下的功能稱為
char hostname_buffer[1024];
get_property(c, Hostname, hostname_buffer);
實際上,這是“正確”的方法,除了需要采取某些步驟以確保不會將目標緩沖區超出某個較長的屬性值之外。
2) void *target
參數指向char *
類型的指針,該指針應該從屬性接收hostname
指針值。 (在這種情況下, target
實際上擁有一個char **
值。)代碼看起來像
case Hostname:
{
char **hostname = target;
*target = c->hostname;
}
break;
這種情況下的功能稱為
char *hostname;
get_property(c, Hostname, &hostname);
第二個變體對我來說並不好,因為在這種情況下,您實際上返回的是指向屬性結構內部數據的指針。 讓外部世界訪問[假定不透明]數據結構的內部不是一個好主意。
PS One通常不需要顯式轉換為C語言中的void *
指針。
讓我們考慮一下get_property
函數的簡化示例,該示例在所有重要方面都相同:
void get_property_hostname(config* c, void* target) {
char * hostname = (char *) target;
*hostname = c->hostname;
}
在函數的第一行,您正在制作“ char *”指針,該指針指向與“ target”相同的位置。 在函數的第二行,當您編寫*hostname = ...
,您正在寫入主機名所指向的char
,因此,您正在寫入target
指向的內存的第一個字節。 這不是您想要的; 您只將一個字節的數據提供給函數的調用者。 另外,編譯器會抱怨,因為賦值的左側類型為“ char”,而右側類型為“ char *”。
至少有三種正確的方法可以在C中返回字符串:
1)返回一個指向原始字符串的指針
如果執行此操作,則用戶將有權訪問該字符串,並且可以根據需要對其進行修改。 您必須告訴他不要這樣做。 將const限定符放在上面將有助於實現這一目標。
const char * get_property_hostname(config* c) {
return c->hostname;
}
2)復制字符串並返回指向重復項的指針
如果這樣做,函數的調用者必須在使用完后將重復的字符串傳遞給free()
。 請參閱strdup的文檔 。
const char * get_property_hostname(config * c) {
return strdup(c->hostname);
}
3)將字符串寫入調用者已分配的緩沖區
如果這樣做,則取決於函數的調用者何時以及如何分配和釋放內存。 這是Microsoft Windows操作系統中許多API所做的事情,因為它為函數調用者提供了最大的靈活性。
void get_property_hostname(config * c, char * buffer, int buffer_size)
{
if (strlen(c->hostname)+1 > buffer_size)
{
// Avoid buffer overflows and return the empty string.
buffer[0] = 0;
}
else
{
strcpy(buffer, c->hostname);
}
}
然后,要使用此功能,您可以執行以下操作:
void foo(){
char buffer[512];
get_property_hostname(c, buffer, sizeof(buffer));
...
// buffer is on stack, so it gets freed automatically when foo returns
}
編輯1:我將把它留給您作為練習,以弄清楚如何將此處介紹的思想重新集成到您的通用get_property
函數中。 如果您花時間了解這里發生的事情,這應該不太困難,但是您可能必須添加一些額外的參數。
編輯2:這是您將方法1改編為使用指向char *的void指針而不是返回值的方法:
void get_property_hostname(config* c, void * target) {
*(char **)target = c->hostname;
}
然后,您將這樣稱呼它:
void foobar() {
char * name;
get_property_hostname(c, &name);
...
}
get_hostname_into_here
定義為:
char *get_hostname_into_here;
您正在傳遞對其的引用,即char**
。 在get_property
,您將void*
強制轉換為char*
而不是char**
,然后在賦值之前取消引用。 為了正確獲取字符串,請使用:
case Hostname:
{
char **hostname;
hostname = (char **) target;
*hostname = c->hostname;
}
break;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.