![](/img/trans.png)
[英]Is it legal to cast a function returning an object pointer to a function returning a void pointer?
[英]Is it legal to cast a function to a pointer to a function of various kinds per C standard?
我分析了一些用 C 編寫的源代碼,發現如下代碼片段:
#include <stdio.h>
struct base_args_t {
int a0;
};
struct int_args_t {
struct base_args_t base;
int a1;
};
struct uint_args_t {
struct base_args_t base;
unsigned int a1;
};
void print_int(struct int_args_t *a)
{
// print int
printf("%i\n", a->a1);
return;
}
void print_uint(struct uint_args_t *a)
{
// print unsigned int
printf("%u\n", a->a1);
return;
}
int main()
{
struct uint_args_t uint_args = {.a1 = 7};
typedef void (*f_print_type)(struct int_args_t *);
void (*print)(struct int_args_t *a) = (f_print_type)print_uint;
print((void *)&uint_args);
return 0;
}
我想知道是否允許將 function 轉換為指向各種 function 的指針,如示例中所做的那樣:
void (*print)(struct int_args_t *a) = (f_print_type)print_uint;
PS 此外,我已經使用啟用的 CFI sanitizer 測試了這個示例,它說:
運行時錯誤:間接 function 調用期間類型“void (struct int_args_t *)”的控制流完整性檢查失敗
但很難說它是否 100% 正確。
對 function 指針的調用必須使用與 function 類型相同的類型。
在main()
語句中print(...);
使用void (*print)(struct int_args_t *a)
調用print_uint
但print_uint
的類型為void print_uint(struct uint_args_t *a)
。 調用是未定義的行為。
根據 C 標准將 function 轉換為指向各種 function 的指針是否合法?
如果允許將 function 轉換為指向各種 function 的指針 [...]
當另一種類型是 function 指針時,轉換或強制轉換始終是安全的。 任何 function 指針始終可以轉換為任何其他函數指針類型。 You have to call the function with the same function pointer type as it is (more exact, the function has to be called with a compatible function pointer type).
根據 C 標准將指向 function 的指針轉換為指向各種 function 的指針是否合法?
是的,您可以將 function 指針分配給任何其他類型的 function 指針:
"指向一種類型的 function 的指針可以轉換為指向另一種類型的 function 的指針,然后再返回;結果應與原始指針比較。如果使用轉換后的指針調用類型不兼容的 ZC1C425268E68384F11 的 ZC1C425268E68384D1對於指向的類型,行為是未定義的。”
來源:C11,6.3.2.3/8
所以任務:
void (*print)(struct int_args_t *a) = (f_print_type)print_uint;
是正確和合法的。
調用未定義行為的是使用指針print()
來引用調用print_uint
:
print((void *)&uint_args);
因為:
“如果使用轉換后的指針調用類型與指向類型不兼容的 function,則行為未定義。”
print_uint
類型
“帶有struct uint_args_t
參數的函數返回void
”
與類型不兼容
“帶有struct int_args_t
參數返回void
的函數”, print
被聲明指向。
參數的類型和被調用的指針不同。
這些struct
本身並不相同也不兼容。
關於兼容性:
對於兩個要兼容的 function 類型,兩者都應指定兼容的返回類型127 。
此外,參數類型列表(如果兩者都存在)應在參數數量和省略號終止符的使用方面達成一致; 相應的參數應具有兼容的類型。 如果一種類型具有參數類型列表,而另一種類型由 function 定義的一部分且包含空標識符列表的 function 聲明符指定,則參數列表不應有省略號終止符,並且每個參數的類型應與應用默認參數提升所產生的類型兼容。 如果一種類型具有參數類型列表,而另一種類型由包含(可能為空)標識符列表的 function 定義指定,則兩者在參數數量上應一致,並且每個原型參數的類型應與類型兼容這是由於將默認參數提升應用於相應標識符的類型而產生的。 (在確定類型兼容性和復合類型時,每個用 function 或數組類型聲明的參數都被認為具有調整后的類型,每個用限定類型聲明的參數都被認為具有其聲明類型的非限定版本。)
- 如果 function 類型都是“舊式”,則不比較參數類型。
資料來源:C18,§6.7.6.3/15
如果它們的類型相同,則兩種類型具有兼容的類型。 用於確定兩種類型是否兼容的附加規則在 6.7.2 中描述了類型說明符,在 6.7.3 中描述了類型限定符,在 6.7.6 中描述了聲明符。56)
56) 兩種類型不必相同才能兼容。
資料來源:C18,§6.2.7/1
示例 2在聲明之后
typedef structs1 { int x; } t1, *tp1; typedef structs2 { int x; } t2, *tp2;
類型
t1
和tp1
指向的類型是兼容的。 類型t1
也與類型structs1
兼容,但與類型structs2
、t2
、tp2
指向的類型或int
不兼容。C18,§6.7.8/5
不同標簽的兩個結構永遠不會兼容,即使它們具有相同的成員集和 alignment,但這里也不是這種情況,因為成員a
的類型在兩種結構類型之間是不同的。
在這種特定情況下,它是完全安全的,因為:
int_args_t
和uint_args_t
是相同的,memory 布局方式。 具體來說, int
和uint
是相同的(沒有有符號/無符號寄存器或 memory 位置之類的東西)。
即使 1 不正確,兩個 function 定義也具有相同的簽名——它們接收一個指針並返回 void。
function 主體在裝配級別上也是相同的,因為您使用與收到的指針相同的偏移量的相同字段,並且您使用的字段具有相同的 memory 布局(如 1 中所述)。
如果您將其全部剝離為編譯器生成的基本匯編代碼,那么您的代碼是完全安全的。 消毒劑告訴您的是,您定義的實際 C 類型不能互換,但這最終無關緊要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.