2011-09-04 76 views
0

我有一段C代碼的工作示例,我正在使用它來教導自己如何在非平凡應用程序中有效使用指針。 (我有一個夢想貢獻缺少功能一個C庫,我靠。)在通用訪問器函數中使用void指針ANSI 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*) - 因此我正在尋找一種解除引用無效指針的方法;我能夠成功實現這個爲int,但一直在踢我的腳後跟,試圖找出如何做到這一點char*(關於void*int有一定道理,但我無法想象如何去做void* to char*

我的成功實施(有測試)是在我的項目的Github分支上,here

我來最接近的是:

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_herenull,提高預警(我想不出:)

untitled: In function ‘get_property’: 
untitled:33: warning: assignment makes integer from pointer without a cast 

我在這裏設計的例子的完整源代碼;請在回答解釋時,或者推薦您使用void指針和/或C風格的任何閱讀時,似乎每個人都有不同的想法,並且我在現實世界中認識的幾個人只是說「圖書館正在這樣做錯誤的,不要使用void指針) - 雖然它會很好,如果庫會公開結構;對於封裝和其他很好的理由,我認爲在這種情況下void指針,通用函數的方法是完全合理的

所以,我在我的get_property()功能的char*NULL調用get_property(c, Hostname, &get_hostname_into_here);

hostname分支做錯了

full source code for example (with output)

+0

我不是100%清楚這裏的目標是什麼,但是'void *'的替代可能是基於'union'的。 –

+0

在'Hostname'的情況下,'target'指向什麼?一個char *'?一個'char'緩衝區?這裏有什麼意圖的語義? (不知道這個,不可能回答你的問題。) –

+0

有兩個問題,就我所知,我沒有辦法事先知道緩衝區應該多大,所以我認爲'target'必須是' char *' - 我無法預先分配我自己的空間。我期望這將意味着配置被釋放後,我的'目標'將是段默認的領土,這是可以的。 –

回答

1

就像我在上面的評論中所說的,不可能給出一個確切的答案,因爲目前還不清楚這裏的目標是什麼。但我看到兩種可能性:

1:targetchar緩衝

如果是這樣的話指向,那就似乎,你需要將字符串的內容複製到緩衝區。這是不可能的,因爲你不知道接收緩衝區有多大。但是,如果你不關心這一點,那麼你需要做的是這樣的:

strcpy((char *)target, c->hostname); 

2:targetchar *

指向如果是這種情況,那麼用意大概是要麼修改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 *。編譯器告訴你,這種轉換沒有任何意義。如果我是你,我會設置你的編譯器將警告視爲錯誤(例如GCC的-Werror標誌),因爲應始終遵守警告!

1

get_property功能應該改變,使target是雙空指針,這意味着你可以改變指針本身(不僅是指在內存):

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); 
+0

你可以把它放在原位,我不確定我想要取代哪個線的例子嗎? –

+0

我認爲這個函數的目的是修改'void *'指向的任何東西。這不是你的代碼所做的。 –

+0

Oli,絕對正確,我澄清了我的問題。 –

0

讓我們考慮您的get_property功能的一個簡單的例子是在所有重要方面是相同的:

void get_property_hostname(config* c, void* target) { 
    char * hostname = (char *) target; 
    *hostname = c->hostname; 
} 

在功能上的第一行,你是一個「字符*」指針指向與「目標」相同的位置。在函數的第二行中,當您編寫*hostname = ...時,您正在寫入主機名指向的char,因此您正在寫入target指向的第一個內存字節。這不是你想要的;你只給一個字節的數據給函數的調用者。另外,編譯器會抱怨,因爲賦值的左邊是類型「char」,而右邊是類型「char *」。

有至少三種正確的方式在C返回一個字符串:

1)返回一個指向原始字符串

如果你這樣做,用戶將有機會獲得字符串,並可以修改它,如果他想。你必須告訴他不要這樣做。把const限定符放在它上面將有助於實現這一點。

const char * get_property_hostname(config* c) { 
    return c->hostname; 
} 

2)複製字符串並返回一個指向該副本

如果你這樣做,函數的調用者必須重複的字符串傳遞給free()當他用它來完成。請參閱documentation of 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 get_property_hostname(config* c, void * target) { 
    *(char **)target = c->hostname; 
} 

那麼你會這樣稱呼它:

void foobar() { 
    char * name; 
    get_property_hostname(c, &name); 
    ... 
} 
+0

大衛,謝謝你的回答,不幸的是我不能改變函數簽名,我試圖擴展現有的庫,但值得注意的是,使用'void *'對於初學者來說有點棘手,爲一口井投票書面答覆,謝謝。 –

+0

我很高興你讚賞它。您不必更改函數簽名......查看「編輯2」,它向您展示瞭如何使用void *而不是返回值。 –

0

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; 
1

有沒有回答你的問題,直到您提供有關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 **值)的代碼將如下

​​

功能在這種情況下會被稱爲

char *hostname; 
get_property(c, Hostname, &hostname); 

該第二變看起來並不好我,因爲在這種情況下,你基本上是返回一個指向屬性結構內部數據的指針。給外部世界訪問[據稱不透明的]數據結構的內部結構並不是一個好主意。

P.S.一個通常不需要明確地在C語言中向和從void *指向者。

+0

Andrey感謝您的回答,Oli給出了同樣全面的答案,包括'get_property'分配自己的緩衝區的方法。爲了解決問題而寫出一個寫得很好的答案。 –