2014-01-27 45 views
3

一起使用盡管清理屬性是一個僅由GCC/Clang支持的擴展,我認爲它是純C中最接近RAII的擴展。爲什麼GCC/Clang的清理屬性不能與功能參數

#define loc_str __attribute__((cleanup(free_loc_str))) 

void free_loc_str(char **str) 
{ if(str && *str) free(*str); } 

int main(void) 
{ 
    loc_str char *s = malloc(10); 
    return 0; // Great! s is freed when it exit its scope 
} 

雖然,該屬性只適用於自動範圍,但不適用於函數參數。即

void func(loc_str char *str) 
{ 
    return; // XXX - str will not be freed (compiled without any warning) 
} 

我已經知道上面的情況,但是,爲什麼?是否有任何理由製造這種限制?

- 更新 -

觸發此問題的一個完整的故事:

我試圖創建C.繼共享指針(或智能指針)是一個非線程安全的,簡單的代碼片段

struct impl_t; 
struct impl_t* ctor(); 
void dtor(struct impl_t* inst); 

struct shared_ptr_s 
{ 
    struct impl_t* inst; 
    int *use_cnt; 
}; 

void free_shared(struct shared_ptr_s* ptr) 
{ 
    if(!ptr) return; 
    if(0 == --(*ptr->use_cnt)) { 
     dtor(ptr->inst); 
     free(ptr->use_cnt); 
    } 
    ptr->inst = 0; 
    ptr->use_cnt = 0; 
} 

#define shared_ptr struct shared_ptr_s __attribute__((cleanup(free_shared))) 

void func(shared_ptr sp) 
{ 
    // shared_ptr loc_sp = sp; // works but make no sense 
    return; // sp will not be freed since cleanup function is not triggered 
} 

int main(void) 
{ 
    shared_ptr sp = { 
     .inst = ctor(), 
     .use_cnt = malloc(sizeof(int)) 
    }; 
    ++*sp.use_cnt; // please bear this simplification. 

    {  
     ++*sp.use_cnt; 
     shared_ptr sp2 = sp; 
    } // sp.inst is still there since use_cnt > 0 

    ++*sp.use_cnt; 
    func(sp); // leak! 

    return 0; 
} 

這就是爲什麼我希望清理屬性可以使用函數參數 - 儘可能手動清除。

+0

main'的'返回類型應該是'int'(我想你已經知道,因爲你有一個'返回0 ;'最後) – Praetorian

+1

'loc_str char * s = malloc(10); FUNC(一個或多個);返回0;'雙免費的bug。 –

+0

@Praetorian坦克供你修正。 – Acer

回答

5

大概是因爲,在函數參數的情況下,思想是它是調用者的,而不是被調用者的,管理參數的內存的責任。如果確實需要被調用者在退出時釋放參數,那麼該解決方法非常簡單,只需創建由cleanup屬性裝飾的參數的本地副本即可。

void func(char *str) 
{ 
    loc_str char *str1 = str; 
    return; 
} // now str1 will be free when func exits 

當然,在這種情況下,不使用對調用者傳遞給func()參數清除屬性,或者你有你的手的雙重自由。


爲您的使用情況下,我建議創建遞增使用計數,並聲明shared_ptr類型的局部變量的宏。

#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \ 
               shared_ptr sp_name = sp_in; 

在需要增加使用次數的地方使用該宏。所以你的榜樣,與調試語句,應該是這樣的:

#include <stdlib.h> 
#include <stdio.h> 

struct shared_ptr_s 
{ 
// struct impl_t* inst; 
    int *use_cnt; 
}; 

typedef struct shared_ptr_s shared_ptr_t;   // unadorned type 
#define shared_ptr struct shared_ptr_s __attribute__((cleanup(free_shared))) 

#define SHARED_PTR_GET_ADD_REF(sp_in, sp_name) ++(*sp_in.use_cnt); \ 
               printf("add use_cnt = %d\n", *sp_in.use_cnt); \ 
               shared_ptr sp_name = sp_in; 

void free_shared(struct shared_ptr_s* ptr) 
{ 
    if(!ptr) return; 
    printf("del use_cnt = %d\n", *ptr->use_cnt - 1); 
    if(0 == --(*ptr->use_cnt)) { 
//  dtor(ptr->inst); 
     printf("freeing %p\n", (void *)ptr->use_cnt); 
     free(ptr->use_cnt); 
    } 
// ptr->inst = 0; 
    ptr->use_cnt = 0; 
} 

void func(shared_ptr_t sp) 
{ 
    SHARED_PTR_GET_ADD_REF(sp, sp_loc); 
    return; 
} 

int main(void) 
{ 
    shared_ptr_t sp = {   // original type does not use __attribute__(cleanup) 
//  .inst = ctor(), 
     .use_cnt = malloc(sizeof(int)) 
    }; 
    SHARED_PTR_GET_ADD_REF(sp, sp_loc); 

    func(sp_loc); 

    return 0; 
} 

Live demo

+0

你的答案效果很好,但我仍然對爲什麼會有限制感到困惑 - 如果一個函數在其參數中聲明瞭cleanup屬性,這些信息應該足以讓調用者記住被調用者將嘗試清理參數。儘管如此,共享指針是明確管理的。 – Acer

+0

@Acer不幸的是,gcc [docs](http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html)沒有提供任何理由爲什麼你不能將函數參數標記爲__attribute __(cleanup( ...))'。正如我之前猜測的那樣,他們可能會認爲如果支持雙'free()'問題會很容易,特別是在使用類似'#define'的參數類型時。 – Praetorian

+0

是的,他們表示了限制,沒有進一步解釋。也許我應該在他們的郵件列表中提出這個問題。謝謝! – Acer

相關問題