#include <iostream>
#include <random>
using namespace std;
struct TradeMsg {
int64_t timestamp; // 0->7
char exchange; // 8
char symbol[17]; // 9->25
char sale_condition[4]; // 26 -> 29
char source_of_trade; // 30
uint8_t trade_correction; // 31
int64_t trade_volume; // 32->39
int64_t trade_price; // 40->47
};
static_assert(sizeof(TradeMsg) == 48);
char buffer[1000000];
template<class T, size_t N=1>
int someFunc(char* buffer, T* output, int& cursor) {
// read process data from buffer. Return data in output. Set cursor to the last byte read 1.
return cursor (rand() % 20) 1; // dummy code
}
void parseData(TradeMsg* msg) {
int cursor = 0;
cursor = someFunc<int64_t>(buffer, &msg->timestamp, cursor);
cursor = someFunc<char>(buffer, &msg->exchange, cursor);
cursor ;
int i = 0;
// i is GUARANTEED to be <= 17 after this loop,
// edit: the input data in buffer[] guarantee that fact.
while (buffer[cursor i] != ',') {
msg->symbol[i] = buffer[cursor i];
i ;
}
msg->symbol[i] = '\n'; // might access symbol[17].
cursor = cursor i 1;
for (i=0; i<4; i ) msg->sale_condition[i] = buffer[cursor i];
cursor = 5;
//cursor = someFunc...
}
int main()
{
TradeMsg a;
a.symbol[17] = '\0';
return 0;
}
我有這個保證具有可預測大小的結構。在代碼中,有一種情況,程式試圖將值分配給超過其 size msg->symbol[17]= ...的陣列元素。
但是,在這種情況下,只要滿足以下條件,分配就不會造成任何傷害:
它在分配下一個結構成員(sale_condition)之前完成(沒有意外的代碼重新排序)。
它不會修改任何以前的成員(時間戳、交換)。
它不訪問結構之外的任何記憶體。
我讀到這是未定義的行為。但是什么樣的編譯器優化/代碼生成會讓這個出錯?symbol[17]位于結構中間的深處,所以我看不到編譯器如何在它之外生成訪問。假設平臺僅為 x86-64
uj5u.com熱心網友回復:
很多人已經指出除錯模式檢查將在結構的陣列成員范圍之外的訪問時觸發,帶有像gcc -fsanitize=undefined. 除此之外,編譯器使用成員訪問之間不重疊的假設來重新排序實際上執行別名的兩個賦值也是合法的:
@Peter 在評論中指出,允許編譯器假設訪問msg->symbol[i]不會影響其他結構體成員,并且可能會延遲msg->symbol[i] = '\n';到寫入msg->sale_condition[i]. (即下沉該存盤到函式的底部)。
沒有充分的理由希望編譯器單獨在此函式中執行此操作,但也許在行內到某個也在那里存盤了某些內容的呼叫者之后,它可能是相關的。或者僅僅因為在這個思想實驗中存在一個 DeathStation 9000 來破壞你的代碼。
你可以安全地寫這個,雖然 GCC 編譯得更糟
由于char*允許為任何其他物件設定別名,因此您可以char*相對于整個結構的開頭而不是成員陣列的開頭偏移 a 。使用offsetof找到合適的起點是這樣的:
#include <cstddef>
...
((char*)msg offsetof(TradeMsg, symbol))[i] = '\n'; // might access symbol[17].
這完全等同于*((char*)msg offsetof(...) i) = '\n';C []運算子的定義,即使它允許您使用[i]相對于相同位置的索引。
但是,這確實使用 GCC11.2 -O2 編譯為效率較低的 asm。 ( Godbolt ),主要是因為int i, cursor比指標寬度窄。從結構的開頭重做索引的“安全”版本在 asm 中做了更多的索引作業,而不是使用msg offsetof(symbol)它已經用作回圈中基址暫存器的指標。
# original version, with UB if `i` goes past the buffer.
# gcc11.2 -O2 -march=haswell. -O3 fully unrolls into a chain of copy/branch
... partially peeled first iteration
.L3: # do{
mov BYTE PTR [rbx 8 rax], dl # store into msg->symbol[i]
movsx rdi, eax # not read inside the loop
lea ecx, [r8 rax]
inc rax
movzx edx, BYTE PTR buffer[rsi 1 rax] # load from buffer
cmp dl, 44
jne .L3 # }while(buffer[cursor i] != ',')
## End of copy-and-search loop.
# Loops are identical up to this point except for MOVSX here vs. MOV in the no-UB version.
movsx rcx, ecx # just redo sign extension of this calculation that was done repeatedly inside the loop just for this, apparently.
.L2:
mov BYTE PTR [rbx 9 rdi], 10 # store a newline
mov eax, 1 # set up for next loop
# offsetof version, without UB
# same loop, but with RDI and RSI usage switched.
# And with mov esi, eax zero extension instead of movsx rdi, eax sign extension
cmp dl, 44
jne .L3 # }while(buffer[cursor i] != ',')
add esi, 9 # offsetof(TradeMsg, symbol)
movsx rcx, ecx # more stuff getting sign extended.
movsx rsi, esi # including something used in the newline store
.L2:
mov BYTE PTR [rbx rsi], 10
mov eax, 1 # set up for next loop
RCX 計算似乎只是供下一個回圈使用,設定 sale_conditions。
順便說一句,復制和搜索回圈就像 strcpy 但帶有','終止符。不幸的是 gcc/clang 不知道如何優化它;他們編譯成一個緩慢位元組在-A時間回路,使用不受例如一個AVX512BW掩蓋商店mask-1從一個vec == set1_epi8(',')比較,以獲得掩碼選擇的位元組數,在─','而不是逗號元素。(可能需要一點技巧來將最低設定位隔離為唯一設定位,除非始終復制 16 或 17 個位元組與查找','位置分開是安全的,這可以在沒有屏蔽存盤或分支的情況下有效完成。)
如果您使用允許 C99 樣式聯合型別雙關的 C 實作,則另一個選項可能是 achar[21]和之間的struct{ char sym[17], sale[4];}聯合。(這是一個 GNU 擴展,也受 MSVC 支持,但不一定每個 x86 編譯器都支持。)
此外,在風格方面,陰影int i = 0;與for( int i=0 ; i<4 ; i )風格很差。為該回圈選擇一個不同的 var 名稱,例如j. (或者如果有任何有意義的東西,一個更好的名字i必須在多個回圈中生存。)
uj5u.com熱心網友回復:
在幾種情況下:
設定變數保護時:https : //clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
在 C 解釋器中(是的,它們存在):https : //root.cern/cling/
uj5u.com熱心網友回復:
您的符號的大小為 17 然而,您正在嘗試為第 18 個索引分配一個值 a.symbol[17] = '\0';
請記住,您的索引值從 0 開始,而不是 1。
所以你有兩個地方可能會出錯。i can equal 17 這將導致錯誤,而我上面顯示的最后一行將導致錯誤。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/397325.html
