簡體   English   中英

C:將數組傳遞給指針函數

[英]C: Passing array to pointer function

我不確定這個問題是否曾經問過,但是我找不到任何類似的主題。

我正在努力處理以下代碼。 想法是在以后的任何時間擴展r而不編寫大量的if-else語句。 函數( func1func2 ...)采用零或一參數。

void func1() {
    puts("func1");
}

void func2(char *arg){
    puts("func2");
    printf("with arg %s\n", arg);
}

struct fcall {
    char name[16];
    void (*pfunc)();
};

int main() {
    const struct fcall r[] = {
        {"F1", func1},
        {"F2", func2}
    };
    char param[] = "someval";

    size_t nfunc = RSIZE(r); /* array size */
    for(;nfunc-->0;) {
        r[nfunc].pfunc(param);
    }
    return 0;
}

上面的代碼假定所有函數都采用字符串參數,而事實並非如此。 聲明的指針函數原型沒有任何數據類型,以防止出現不兼容的指針類型警告。

將參數傳遞給不帶任何參數的函數通常會導致參數太少 但是在這種情況下,編譯器不會提前“看到”這一點,這也讓我相信沒有做任何優化來排除這些未使用的地址不會被壓入堆棧。 (我沒有看過實際的匯編代碼)。

某種程度上感覺不對,這通常是緩沖區溢出或未定義行為的秘訣。 分別調用不帶參數的函數會更好嗎? 如果是這樣,這會造成多大的損失?

這樣做的方法是typedef一個帶有1個參數的函數,因此編譯器可以驗證是否傳遞了正確數量的參數,並且沒有傳遞絕對不兼容的內容(例如,按值構造)。 並且在初始化數組時,請使用此typedef強制轉換函數類型。

void func1(void) { ... }

void func2(char *arg) { ... }

void func3(int arg) { ... }

typedef uintptr_t param_t;    
typedef void (*func_t)(param_t);

struct fcall {
    char name[16];
    func_t pfunc;
};

const struct fcall r[] = {
    {"F1", (func_t) func1},
    {"F2", (func_t) func2}
    {"F3", (func_t) func3}
};

...

r[0].pfunc((param_t) "foo");
r[1].pfunc((param_t) "bar");
r[2].pfunc((param_t) 1000);

在這里, param_t被定義為uintpr_t 這是一個足夠存儲指針值的整數類型。 有關詳細信息,請參見此處: 什么是uintptr_t數據類型 需要注意的是, param_t的調用約定應與您使用的函數參數兼容。 通常對於所有整數和指針類型都是如此。 下面的示例將起作用,所有類型轉換在調用約定方面都相互兼容:

// No problem here.
void ptr_func(struct my_struct *ptr) {
    ...
}
...
struct my_struct struct_x;
((func_t) &ptr_func)((param_t) &struct_x);

但是,如果您要傳遞一個float或double參數,則它可能無法按預期工作。

// There might be a problem here. Depending on the calling
// conventions the value might contain a complete garbage,
// as it might be taken from a floating point register that
// was not set on the call site.
void float_func(float value) {
    ...
}
...
float x = 1.0;
((func_t) &float_func)((param_t) x);

在這種情況下,您可能需要定義如下函數:

// Problem fixed, but only partially. Instead of garbage
// there might be rounding error after the conversions.
void float_func(param_t param) {
    float value = (float) param;
    ...
}
...
float x = 1.234;
((func_t) &float_func)((param_t) x);

浮點數首先被轉換為整數類型,然后返回。 結果,該值可能會四舍五入。 一個明顯的解決方案是獲取x的地址並將其傳遞給函數float_func2(float *value_ptr)進行修改。 該函數將取消引用其指針參數並獲取實際的float值。

但是,當然,作為硬核C程序員,我們不想變得顯而易見,因此我們將采取一些丑陋的詭計。

// Problem fixed the true C-programmer way.
void float_func(param_t param) {
    float value = *((float *) &param);
    ...
}
...
float x = 1.234;
((func_t) &float_func)(*((param_t *) &x));

與將指針傳遞給float相比,此示例的不同之處在於,在將參數傳遞到寄存器而非堆棧上的體系結構(如x86-64)上,足夠聰明的編譯器可以使float_func僅使用寄存器來完成其工作,無需從內存中加載參數。

一種選擇是,所有函數都接受char *參數,並且您的調用代碼始終傳遞一個。 不需要參數的函數不必使用它們收到的參數。

為了保持整潔(並避免未定義的行為),如果必須具有不接受參數的某些函數和必須接受參數的某些函數,請使用兩個列表並分別注冊/調用每種類型的函數。

如果行為未定義,則無法確定會造成多大的損害。

可能炸毀地球。 否則可能不會。

所以只是不要這樣做,好嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM