我目前正在嘗試掌握 C 嚴格別名規則,而根據我目前的理解,這段代碼違反了它們。
我們已經將緩沖區指標轉換為結構設定指標,按照 C 標準,它應該會導致未定義的行為,對吧?
static inline void libusb_fill_control_transfer(
struct libusb_transfer *transfer, libusb_device_handle *dev_handle,
unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data,
unsigned int timeout)
{
struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer;
transfer->dev_handle = dev_handle;
transfer->endpoint = 0;
transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL;
transfer->timeout = timeout;
transfer->buffer = buffer;
if (setup)
transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE
libusb_le16_to_cpu(setup->wLength));
transfer->user_data = user_data;
transfer->callback = callback;
}
編輯。
這是 libusb 專案的一部分https://github.com/libusb/libusb/blob/7ffad5c137ed4c1d8a3ac485f35770fb979ca53a/libusb/libusb.h#L1578
編輯 2。
添加libusb_control_setup結構定義
struct libusb_control_setup {
/** Request type. Bits 0:4 determine recipient, see
* \ref libusb_request_recipient. Bits 5:6 determine type, see
* \ref libusb_request_type. Bit 7 determines data transfer direction, see
* \ref libusb_endpoint_direction.
*/
uint8_t bmRequestType;
/** Request. If the type bits of bmRequestType are equal to
* \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD
* "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to
* \ref libusb_standard_request. For other cases, use of this field is
* application-specific. */
uint8_t bRequest;
/** Value. Varies according to request */
uint16_t wValue;
/** Index. Varies according to request, typically used to pass an index
* or offset */
uint16_t wIndex;
/** Number of bytes to transfer */
uint16_t wLength;
};
uj5u.com熱心網友回復:
假設引數buffer實際上并不指向一個型別的物件struct libusb_control_setup(可能是一個unsigned char陣列?),那么是的,這是一個嚴格的別名違規,它是未定義的行為。
管理別名的規則在C 標準的第 6.5p7 節中指定:
物件的存盤值只能由具有以下型別之一的左值運算式訪問:88)
- 與物件的有效型別兼容的型別,
- 與物件的有效型別兼容的型別的限定版本,
- 與物件的有效型別相對應的有符號或無符號型別,
- 對應于物件有效型別的限定版本的有符號或無符號型別,
- 聚合或聯合型別,在其成員中包括上述型別之一(遞回地包括子聚合或包含聯合的成員),或
- 一種字符型別。
88 ) 此串列的目的是指定物件可能或可能不別名的情況。
請注意,這不包括將char陣列視為其他型別,盡管允許反過來。
處理此問題的正確方法是創建給定型別的本地結構,然后用于memcpy復制位元組。
struct libusb_control_setup setup;
memcpy(&setup, buffer, sizeof setup);
uj5u.com熱心網友回復:
作為@dbush 答案的補充
關于提供的示例,我就是這樣做的,但是由于這是來自受人尊敬的專案的代碼,顯然在很多地方都使用了它,所以我不確定該怎么想。
許多程式員使用指標雙關語,他們認為這是安全的,因為它適用于他們的計算機。許多程式員還認為 usingmemcpy會使他們的代碼效率降低,并且更貪記憶體。
在大多數情況下(當然可能的話)編譯器會優化memcpy呼叫
例子:
typedef struct
{
int a;
double b;
int (*callback)(int);
}mt;
int foo(char *ptr, int par)
{
mt m;
memcpy(&m, ptr, sizeof(m));
printf("%f\n", m.b);
m.callback(par);
return m.a;
}
x86 編譯器生成代碼:
.LC0:
.string "%f\n"
foo:
push rbp
mov ebp, esi
sub rsp, 32
movdqu xmm1, XMMWORD PTR [rdi]
mov rax, QWORD PTR [rdi 16]
mov edi, OFFSET FLAT:.LC0
movaps XMMWORD PTR [rsp], xmm1
movsd xmm0, QWORD PTR [rsp 8]
mov QWORD PTR [rsp 16], rax
mov eax, 1
call printf
mov edi, ebp
call [QWORD PTR [rsp 16]]
mov eax, DWORD PTR [rsp]
add rsp, 32
pop rbp
ret
但是 ARM Cortex M0 會呼叫memcpy未對齊版本的指標會導致硬體例外。
.LC0:
.ascii "%f\012\000"
foo:
push {r4, lr}
movs r4, r1
sub sp, sp, #32
movs r1, r0
movs r2, #24
add r0, sp, #8
bl memcpy
ldr r2, [sp, #16]
ldr r3, [sp, #20]
ldr r0, .L3
str r2, [sp]
str r3, [sp, #4]
bl printf
movs r0, r4
ldr r3, [sp, #24]
blx r3
ldr r0, [sp, #8]
add sp, sp, #32
pop {r4, pc}
.L3:
.word .LC0
uj5u.com熱心網友回復:
使用結構或聯合型別從外部提供的字符緩沖區讀取或寫入資料會帶來兩個潛在問題:
該標準允許其用戶永遠不需要以不同型別和不同時間范圍的許可訪問存盤的實作假設程式不會執行此類訪問。雖然它明確承認存在的實作保證所有對物件的訪問都將被視為使用托管執行環境的精確語意對底層存盤的訪問(參見 N1570 5.1.2.3 第 9 段),無論標準是否需要它們這樣做,并允許符合(但不嚴格)符合程式專門針對此類實作,它沒有區分提供此類保證的實作和不提供此類保證的實作。
將指標轉換為結構或聯合型別的行為只有在指標滿足其每個成員的最嚴格對齊要求時才具有定義的行為。如果 struct 或 union 有一個成員的對齊要求不滿足指標,則強制轉換為 struct 或 union 型別將呼叫未定義行為,并且在某些實際實作中可能會產生錯誤代碼,即使指標滿足實際使用的所有成員的對齊要求。
第一個問題可以通過使用適合手頭任務的編譯器配置來解決。但是,即使使用-fno-strict-aliasing dialect. 給定類似的東西:
#include <string.h>
struct foo { short x, y; int z; } sf;
void test1(void *p)
{
memcpy(&sf, p, sizeof (struct foo));
}
void test2(void *p)
{
struct foo *foop = p;
memcpy(&sf, foop, sizeof (struct foo));
}
當以 Cortex-M0 為目標時,clang 將生成的代碼test1效率將比字對齊test2的情況低得多(幾乎是 4:1 的速度和空間損失),但如果 p 不是,則代碼將失敗字對齊,而較慢的代碼無論如何都可以作業。ptest2test1
因此,即使在使用 時memcpy,在決定是否將指標轉換為結構型別時,也應該考慮關于對齊的已知資訊。如果對齊已知,這樣的強制轉換可能會大大提高效率,但如果不知道,則會導致程式崩潰。
uj5u.com熱心網友回復:
Libusb 是用 C 語言撰寫的。
為了應對 C 嚴格的別名規則,用不同的語言撰寫應用程式就足夠了。Assembly、Haskell、Java、Rust,甚至 C (是的!)都很好。C 嚴格的別名規則不適用于這些語言。
因此,從 libusb 的角度來看,buffer引數只是神奇地突然出現,其中沒有任何 C 物件。
C標準對這種情況有什么要求嗎?當然不是。該標準允許 C 程式與用其他語言撰寫的代碼進行互動,但沒有規定具體的規則。因此,就 C 標準而言,這是未定義的行為。讓我重申一下:C 與其他語言的任何互動都是 UB。然而,出于某種原因,即使是最頑固的 C 純粹主義者也對這種特定型別的未定義行為感到滿意,因此我們可以假設這是可以的。
但是,如果你真的喜歡 C 怎么辦?
您可以用 C 撰寫代碼,然后使用 C 編譯器(請記住,對于 C-to-C 互動沒有嚴格的別名規則)。但這太冒險了。語言出現了分歧,相同的結構有時在其中具有不同的含義。所以這不是一種可擴展的方法。
直到現在還沒有解決方案。但我發明了一個解決方案,特此將我的發明發布到公共領域。
就是這樣:用一種叫做 C?? 的新語言撰寫您的客戶端應用程式。這種語言與 C 語言并沒有太大的不同。它的標準可以通過采用 C 標準(您想要的任何版本)并將所有相關的識別符號替換C為C??. (小心,不要替換所有出現的地方,否則會使某些代碼示例無效)。該語言有幾個免費和商業編譯器現成可用(gcc、clang 和 msvc 或多或少都是有能力的 C?? 編譯器)。
C?? 語言有自己嚴格的別名規則,但當然它們只適用于 C?? 程式。每當在 C 中創建物件時?并且它的地址被傳遞給 C 代碼(反之亦然),沒有規定相關 C 和 C 的相似或不同的規則?型別應該是。它們永遠不是“相同型別”或“兼容型別”或類似的東西,因為沒有規則來管理 C 和 C 的兼容性?型別。所以一個可以是,說,libusb_control_setup另一個,說,,char[128]與兩種型別都被呼叫的情況相比,我們的情況并沒有好或壞libusb_control_setup。
所以是的,我們已經將一種 UB 換成了另一種。一個讓所有人都被激怒的,一個沒有人說過任何反對的話。我會說這是一個很好的交易。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/419675.html
標籤:
