คำจำกัดความของคำว่า "ระเหย" นี้มีความผันผวนหรือ GCC มีปัญหาการปฏิบัติตามมาตรฐานหรือไม่


89

ฉันต้องการฟังก์ชันที่ (เช่น SecureZeroMemory จาก WinAPI) หน่วยความจำเป็นศูนย์เสมอและไม่ได้รับการปรับให้เหมาะสมแม้ว่าคอมไพเลอร์จะคิดว่าหน่วยความจำนั้นจะไม่ถูกเข้าถึงอีกเลยหลังจากนั้น ดูเหมือนผู้สมัครที่สมบูรณ์แบบสำหรับความผันผวน แต่ฉันมีปัญหาบางอย่างในการทำให้ GCC ทำงานได้จริง นี่คือฟังก์ชั่นตัวอย่าง:

void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
    volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;

    while (size--)
    {
        *bytePtr++ = 0;
    }
}

ง่ายพอ แต่รหัสที่ GCC สร้างขึ้นจริงถ้าคุณเรียกว่ามันแตกต่างกันอย่างมากกับเวอร์ชันคอมไพเลอร์และจำนวนไบต์ที่คุณพยายามเป็นศูนย์ https://godbolt.org/g/cMaQm2

  • GCC 4.4.7 และ 4.5.3 ไม่เคยเพิกเฉยต่อความผันผวน
  • GCC 4.6.4 และ 4.7.3 ละเว้นความผันผวนสำหรับอาร์เรย์ขนาด 1, 2 และ 4
  • GCC 4.8.1 ถึง 4.9.2 ละเว้นความผันผวนสำหรับขนาดอาร์เรย์ 1 และ 2
  • GCC 5.1 ถึง 5.3 ละเว้นความผันผวนสำหรับขนาดอาร์เรย์ 1, 2, 4, 8
  • GCC 6.1 จะละเว้นมันสำหรับขนาดอาร์เรย์ใด ๆ (คะแนนโบนัสสำหรับความสม่ำเสมอ)

คอมไพเลอร์อื่น ๆ ที่ฉันได้ทดสอบ (เสียงดัง, icc, vc) สร้างที่เก็บที่คาดหวังด้วยเวอร์ชันคอมไพเลอร์และขนาดอาร์เรย์ใด ๆ ณ จุดนี้ฉันสงสัยว่านี่เป็นข้อผิดพลาดของคอมไพเลอร์ GCC (ค่อนข้างเก่าและรุนแรง) หรือเป็นคำจำกัดความของการระเหยในมาตรฐานที่ไม่แน่ชัดว่านี่เป็นพฤติกรรมที่สอดคล้องกันจริง ๆ ทำให้ไม่สามารถเขียนแบบพกพาได้ " SecureZeroMemory "ฟังก์ชัน?

แก้ไข: ข้อสังเกตที่น่าสนใจ

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <atomic>

void callMeMaybe(char* buf);

void volatileZeroMemory(volatile void* ptr, std::size_t size)
{
    for (auto bytePtr = static_cast<volatile std::uint8_t*>(ptr); size-- > 0; )
    {
        *bytePtr++ = 0;
    }

    //std::atomic_thread_fence(std::memory_order_release);
}

std::size_t foo()
{
    char arr[8];
    callMeMaybe(arr);
    volatileZeroMemory(arr, sizeof arr);
    return sizeof arr;
}

การเขียนที่เป็นไปได้จาก callMeMaybe () จะทำให้ GCC ทุกเวอร์ชันยกเว้น 6.1 สร้างร้านค้าที่คาดไว้การแสดงความคิดเห็นในรั้วหน่วยความจำจะทำให้ GCC 6.1 สร้างร้านค้าแม้ว่าจะใช้ร่วมกับการเขียนที่เป็นไปได้จาก callMeMaybe () เท่านั้น

มีคนแนะนำให้ล้างแคชด้วย Microsoft ไม่พยายามล้างแคชเลยใน "SecureZeroMemory" แคชมีแนวโน้มที่จะถูกยกเลิกอย่างรวดเร็วอยู่แล้วดังนั้นนี่อาจไม่ใช่เรื่องใหญ่ นอกจากนี้หากโปรแกรมอื่นพยายามตรวจสอบข้อมูลหรือกำลังจะเขียนลงในไฟล์เพจก็จะเป็นเวอร์ชันที่เป็นศูนย์เสมอ

นอกจากนี้ยังมีข้อกังวลบางประการเกี่ยวกับ GCC 6.1 โดยใช้ memset () ในฟังก์ชันสแตนด์อโลน คอมไพเลอร์ GCC 6.1 บน godbolt อาจสร้างที่เสียหายเนื่องจาก GCC 6.1 ดูเหมือนจะสร้างลูปปกติ (เช่น 5.3 ทำบน godbolt) สำหรับฟังก์ชันแบบสแตนด์อโลนสำหรับบางคน (อ่านความคิดเห็นของคำตอบของ zwol)


4
การใช้ IMHO volatileเป็นข้อบกพร่องเว้นแต่จะพิสูจน์เป็นอย่างอื่น แต่ส่วนใหญ่จะเป็นจุดบกพร่อง volatileถูกระบุว่าเป็นอันตรายมาก - อย่าใช้มัน
Jesper Juhl

19
@JesperJuhl: ไม่volatileเหมาะสมในกรณีนี้
Dietrich Epp

9
@NathanOliver: ไม่ได้ผลเพราะคอมไพเลอร์สามารถปรับแต่งร้านค้าที่ตายแล้วให้เหมาะสมแม้ว่าจะใช้memset. ปัญหาคือคอมไพเลอร์รู้ว่าmemsetทำอะไร
Dietrich Epp

8
@PaulStelian: นั่นจะเป็นvolatileตัวชี้เราต้องการตัวชี้volatile(เราไม่สนใจว่า++จะเข้มงวด แต่จะ*p = 0เข้มงวดหรือไม่)
Dietrich Epp

7
@JesperJuhl: ไม่มีอะไรระบุเกี่ยวกับความผันผวนต่ำกว่านี้
GManNickG

คำตอบ:


82

พฤติกรรมของ GCC อาจสอดคล้องกันและแม้ว่าจะไม่เป็นเช่นนั้นคุณก็ไม่ควรพึ่งพาvolatileทำสิ่งที่ต้องการในกรณีเช่นนี้ คณะกรรมการ C ที่ออกแบบมาvolatileสำหรับการลงทะเบียนฮาร์ดแวร์ที่แมปหน่วยความจำและสำหรับตัวแปรที่แก้ไขระหว่างขั้นตอนการควบคุมที่ผิดปกติ (เช่นตัวจัดการสัญญาณและsetjmp) สิ่งเหล่านี้เป็นเพียงสิ่งเดียวที่เชื่อถือได้ ไม่ปลอดภัยที่จะใช้เป็นคำอธิบายประกอบ "อย่าปรับให้เหมาะสมที่สุด" โดยทั่วไป

โดยเฉพาะอย่างยิ่งมาตรฐานยังไม่ชัดเจนในประเด็นสำคัญ (ฉันได้แปลงรหัสของคุณเป็น C ไม่ควรมีความแตกต่างระหว่าง C และ C ++ ที่นี่ฉันยังได้ทำการอินไลน์ที่จะเกิดขึ้นก่อนการเพิ่มประสิทธิภาพที่น่าสงสัยด้วยตนเองเพื่อแสดงว่าคอมไพเลอร์ "เห็น" อะไร ณ จุดนั้น .)

extern void use_arr(void *, size_t);
void foo(void)
{
    char arr[8];
    use_arr(arr, sizeof arr);

    for (volatile char *p = (volatile char *)arr;
         p < (volatile char *)(arr + 8);
         p++)
      *p = 0;
}

ห่วงหน่วยความจำเข้าถึงหักบัญชีarrผ่าน lvalue ระเหยที่มีคุณสมบัติ แต่arrตัวเองจะไม่ได้volatileประกาศ ดังนั้นอย่างน้อยที่สุดก็อนุญาตให้คอมไพเลอร์ C อนุมานได้ว่าร้านค้าที่ทำโดยลูปนั้น "ตาย" และลบลูปทั้งหมด มีข้อความใน C เหตุผลที่แสดงให้เห็นว่าคณะกรรมการเป็นความหมายที่จะต้องใช้ร้านค้าเหล่านั้นจะได้รับการเก็บรักษาไว้ แต่มาตรฐานตัวเองไม่ได้จริงทำให้ความต้องการที่เป็นฉันอ่านมัน

สำหรับการอภิปรายเพิ่มเติมเกี่ยวกับสิ่งที่มาตรฐานต้องการหรือไม่ต้องการโปรดดูเหตุใดตัวแปรโลคัลที่มีความผันผวนจึงได้รับการปรับให้เหมาะสมแตกต่างจากอาร์กิวเมนต์ที่ระเหยได้และเหตุใดเครื่องมือเพิ่มประสิทธิภาพจึงสร้างลูปแบบไม่ใช้งานจากส่วนหลัง , การเข้าถึงวัตถุที่ไม่ลบเลือนที่ประกาศผ่านการอ้างอิง / ตัวชี้ที่ระเหยได้เป็นไปตามกฎการระเหยเมื่อมีการเข้าถึงดังกล่าวหรือไม่? และGCC 71,793

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความคิดของ คณะกรรมการvolatileให้ค้นหาC99 Rationaleสำหรับคำว่า "volatile" กระดาษ " Volatiles are Miscompiled " ของ John Regehr แสดงให้เห็นอย่างละเอียดว่าความคาดหวังของโปรแกรมเมอร์volatileอาจไม่เป็นที่พอใจของคอมไพเลอร์การผลิต บทความชุดของทีม LLVM " สิ่งที่โปรแกรมเมอร์ C ทุกคนควรรู้เกี่ยวกับพฤติกรรมที่ไม่ได้กำหนด " ไม่ได้สัมผัสเป็นพิเศษvolatileแต่จะช่วยให้คุณเข้าใจว่าทำไมคอมไพเลอร์ C สมัยใหม่จึงไม่ใช่ "แอสเซมเบลอร์แบบพกพา"


สำหรับคำถามเชิงปฏิบัติเกี่ยวกับวิธีการใช้งานฟังก์ชันที่ทำในสิ่งที่คุณต้องการvolatileZeroMemoryทำ: ไม่ว่ามาตรฐานจะต้องการหรือต้องการอะไรก็ตามจะเป็นการดีที่สุดที่จะสมมติว่าคุณไม่สามารถใช้volatileสำหรับสิ่งนี้ได้ มีเป็นอีกทางเลือกหนึ่งที่สามารถเป็นที่พึ่งให้กับการทำงานเพราะมันจะทำลายสิ่งอื่น ๆ มากเกินไปถ้ามันไม่ได้ทำงาน:

extern void memory_optimization_fence(void *ptr, size_t size);
inline void
explicit_bzero(void *ptr, size_t size)
{
   memset(ptr, 0, size);
   memory_optimization_fence(ptr, size);
}

/* in a separate source file */
void memory_optimization_fence(void *unused1, size_t unused2) {}

อย่างไรก็ตามคุณต้องตรวจสอบให้แน่ใจว่าmemory_optimization_fenceไม่มีการแทรกในสถานการณ์ใด ๆ ต้องอยู่ในซอร์สไฟล์ของตัวเองและต้องไม่อยู่ภายใต้การปรับให้เหมาะสมเวลาลิงก์

มีตัวเลือกอื่น ๆ โดยอาศัยส่วนขยายของคอมไพเลอร์ซึ่งอาจใช้งานได้ในบางสถานการณ์และสามารถสร้างโค้ดที่เข้มงวดมากขึ้น (หนึ่งในนั้นปรากฏในคำตอบรุ่นก่อนหน้านี้) แต่ไม่มีตัวเลือกใดที่เป็นสากล

(ฉันขอแนะนำให้เรียกใช้ฟังก์ชันexplicit_bzeroนี้เนื่องจากมีอยู่ภายใต้ชื่อนั้นในไลบรารี C มากกว่าหนึ่งรายการมีคู่แข่งอย่างน้อยสี่ชื่อ แต่แต่ละชื่อได้รับการรับรองโดยไลบรารี C เดียวเท่านั้น)

คุณควรทราบด้วยว่าแม้ว่าคุณจะสามารถใช้งานได้ แต่ก็อาจไม่เพียงพอ โดยเฉพาะอย่างยิ่งให้พิจารณา

struct aes_expanded_key { __uint128_t rndk[16]; };

void encrypt(const char *key, const char *iv,
             const char *in, char *out, size_t size)
{
    aes_expanded_key ek;
    expand_key(key, ek);
    encrypt_with_ek(ek, iv, in, out, size);
    explicit_bzero(&ek, sizeof ek);
}

สมมติว่าฮาร์ดแวร์ที่มีคำแนะนำการเร่งความเร็ว AES หากexpand_keyและencrypt_with_ekเป็นแบบอินไลน์คอมไพลเลอร์อาจสามารถเก็บไว้ekในไฟล์ทะเบียนเวกเตอร์ได้ทั้งหมดจนกว่าจะมีการเรียกexplicit_bzeroซึ่งบังคับให้คัดลอกข้อมูลที่ละเอียดอ่อนไปยังสแต็กเพื่อลบข้อมูลและ แย่กว่านั้นอย่าทำเรื่องยี้เกี่ยวกับคีย์ที่ยังคงนั่งอยู่ในทะเบียนเวกเตอร์!


6
น่าสนใจ ... ฉันสนใจที่จะดูการอ้างอิงความคิดเห็นของคณะกรรมการ
Dietrich Epp

10
กำลังสองที่มีนิยาม 6.7.3 (7) ของvolatileas [... ] อย่างไรดังนั้นนิพจน์ใด ๆ ที่อ้างถึงวัตถุดังกล่าวจะต้องได้รับการประเมินอย่างเคร่งครัดตามกฎของเครื่องจักรนามธรรมดังที่อธิบายไว้ใน 5.1.2.3 นอกจากนี้ในทุกจุดลำดับค่าสุดท้ายที่เก็บไว้ในวัตถุจะต้องสอดคล้องกับที่กำหนดโดยเครื่องจักรนามธรรมยกเว้นจะแก้ไขโดยปัจจัยที่ไม่รู้จักที่กล่าวถึงก่อนหน้านี้ สิ่งที่ก่อให้เกิดการเข้าถึงอ็อบเจ็กต์ที่มีคุณสมบัติระเหยได้คือการกำหนดการนำไปใช้งาน เหรอ?
Iwillnotexist Idonotexist

15
@IwillnotexistIdonotexist คำสำคัญในทางที่เป็นวัตถุ volatile sig_atomic_t flag;เป็นวัตถุที่ระเหยได้ *(volatile char *)fooเป็นเพียงการเข้าถึงผ่าน lvalue ที่มีคุณสมบัติในการระเหยและมาตรฐานไม่ต้องการให้มีลักษณะพิเศษใด ๆ
zwol

3
มาตรฐานระบุว่าสิ่งที่ต้องเป็นไปตามเกณฑ์ใดจึงจะเป็นการนำไปใช้งานที่ "สอดคล้อง" ไม่มีความพยายามที่จะอธิบายว่าเกณฑ์ใดที่การนำไปใช้งานบนแพลตฟอร์มหนึ่ง ๆ ต้องเป็นไปตามนั้นเพื่อให้เป็นการนำไปใช้งานที่ "ดี" หรือ "ใช้งานได้" การปฏิบัติของ GCC volatileอาจเพียงพอที่จะทำให้การดำเนินการ "สอดคล้อง" แต่ไม่ได้หมายความว่าจะเพียงพอที่จะ "ดี" หรือ "มีประโยชน์" สำหรับการเขียนโปรแกรมระบบหลายประเภทควรถือว่ามีข้อบกพร่องอย่างมากในเรื่องเหล่านั้น
supercat

3
ข้อมูลจำเพาะ C ยังค่อนข้างบอกโดยตรงว่า"การใช้งานจริงไม่จำเป็นต้องประเมินส่วนหนึ่งของนิพจน์หากสามารถอนุมานได้ว่าไม่ได้ใช้ค่าของมันและไม่มีผลข้างเคียงที่จำเป็น ( รวมถึงสิ่งที่เกิดจากการเรียกใช้ฟังก์ชันหรือการเข้าถึงวัตถุที่ระเหยได้ ) .” (เน้นของฉัน)
Johannes Schaub - litb

15

ฉันต้องการฟังก์ชันที่ (เช่น SecureZeroMemory จาก WinAPI) หน่วยความจำเป็นศูนย์เสมอและไม่ได้รับการปรับให้เหมาะสม

นี่คือสิ่งที่ฟังก์ชันมาตรฐานmemset_sมีไว้สำหรับ


เกี่ยวกับว่าพฤติกรรมที่มีความผันผวนนี้เป็นไปตามหรือไม่นั้นเป็นเรื่องยากที่จะพูดและมีการพูดถึงความผันผวนขานมานานแล้วว่ามีข้อบกพร่อง

ประเด็นหนึ่งคือข้อกำหนดระบุว่า "การเข้าถึงวัตถุระเหยได้รับการประเมินอย่างเคร่งครัดตามกฎของเครื่องนามธรรม" แต่นั่นหมายถึง 'วัตถุที่ระเหยได้' เท่านั้นไม่ได้เข้าถึงวัตถุที่ไม่ระเหยผ่านตัวชี้ที่มีการเพิ่มความผันผวน เห็นได้ชัดว่าถ้าคอมไพเลอร์สามารถบอกได้ว่าคุณไม่ได้เข้าถึงวัตถุที่ระเหยได้จริง ๆ ก็ไม่จำเป็นต้องถือว่าวัตถุนั้นระเหยง่าย


4
หมายเหตุ: นี่เป็นส่วนหนึ่งของมาตรฐาน C11 และยังไม่มีให้บริการในห่วงโซ่เครื่องมือทั้งหมด
Dietrich Epp

5
สิ่งหนึ่งที่น่าสนใจคือฟังก์ชันนี้เป็นมาตรฐานสำหรับ C11 แต่ไม่ใช่สำหรับ C ++ 11, C ++ 14 หรือ C ++ 17 ในทางเทคนิคแล้วมันไม่ใช่วิธีแก้ปัญหาสำหรับ C ++ แต่ฉันยอมรับว่านี่ดูเหมือนจะเป็นตัวเลือกที่ดีที่สุดจากมุมมองที่ใช้งานได้จริง ณ จุดนี้ฉันสงสัยว่าพฤติกรรมจาก GCC เป็นไปตามนั้นหรือไม่ แก้ไข: อันที่จริง VS 2015 ไม่มี memset_s ดังนั้นจึงยังไม่พกพาได้ทั้งหมด
cooky451

2
@ cooky451 ฉันคิดว่าC ++ 17 ดึงไลบรารีมาตรฐาน C11 เข้ามาโดยอ้างอิง (ดูอื่น ๆ ที่สอง)
nwp

14
นอกจากนี้การอธิบายmemset_sว่าเป็นมาตรฐาน C11 นั้นเป็นการพูดเกินจริง เป็นส่วนหนึ่งของภาคผนวก K ซึ่งเป็นทางเลือกใน C11 (และเป็นทางเลือกใน C ++ ด้วย) โดยพื้นฐานแล้วผู้ใช้งานทั้งหมดรวมถึง Microsoft ซึ่งมีแนวคิดมาตั้งแต่แรก (!) ได้ปฏิเสธที่จะรับมัน ล่าสุดฉันได้ยินว่าพวกเขาพูดถึงการทิ้งมันใน C-next
zwol

8
@ cooky451 ในบางวงการ Microsoft มีชื่อเสียงในการบังคับให้สิ่งต่างๆเข้าสู่มาตรฐาน C โดยทั่วไปแล้วการคัดค้านของคนอื่น ๆ โดยทั่วไปแล้วไม่ต้องกังวลที่จะนำไปใช้ด้วยตนเอง (ตัวอย่างที่ร้ายแรงที่สุดคือการผ่อนปรนกฎของ C99 สำหรับประเภทพื้นฐานที่size_tอนุญาตให้เป็นได้ Win64 ABI ไม่สอดคล้องกับ C90 นั่นจะเป็น ... ไม่โอเคแต่ก็ไม่น่ากลัว ... ถ้า MSVC ได้หยิบ C99 ขึ้นมาจริง ๆuintmax_tและ%zuในเวลาที่เหมาะสม แต่ก็ไม่ได้ )
zwol

2

ฉันเสนอเวอร์ชันนี้เป็น C ++ แบบพกพา (แม้ว่าความหมายจะแตกต่างกันเล็กน้อย):

void volatileZeroMemory(volatile void* const ptr, unsigned long long size)
{
    volatile unsigned char* bytePtr = new (ptr) volatile unsigned char[size];

    while (size--)
    {
        *bytePtr++ = 0;
    }
}

ตอนนี้คุณสามารถเขียนเข้าถึงวัตถุระเหยได้แล้วได้แล้วไม่ใช่แค่การเข้าถึงวัตถุที่ไม่ลบเลือนซึ่งสร้างขึ้นจากมุมมองที่เปลี่ยนแปลงได้ของวัตถุ

ความแตกต่างทางความหมายคือตอนนี้สิ้นสุดอายุการใช้งานของวัตถุใด ๆ ที่ครอบครองพื้นที่หน่วยความจำอย่างเป็นทางการเนื่องจากหน่วยความจำถูกนำกลับมาใช้ ดังนั้นการเข้าถึงวัตถุหลังจากทำให้เนื้อหาเป็นศูนย์จึงเป็นพฤติกรรมที่ไม่ได้กำหนดไว้อย่างแน่นอน (ก่อนหน้านี้จะเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในกรณีส่วนใหญ่ แต่ก็มีข้อยกเว้นบางประการอย่างแน่นอน)

ในการใช้การทำให้เป็นศูนย์ในช่วงอายุการใช้งานของวัตถุแทนที่จะใช้ในตอนท้ายผู้เรียกควรใช้ตำแหน่ง newเพื่อใส่อินสแตนซ์ใหม่ของประเภทดั้งเดิมกลับมาอีกครั้ง

โค้ดสามารถทำให้สั้นลง (แม้ว่าจะชัดเจนน้อยกว่า) โดยใช้การกำหนดค่าเริ่มต้น:

void volatileZeroMemory(volatile void* const ptr, unsigned long long size)
{
    new (ptr) volatile unsigned char[size] ();
}

และ ณ จุดนี้มันเป็นซับเดียวและแทบไม่รับประกันว่าจะมีฟังก์ชั่นตัวช่วยเลย


2
หากการเข้าถึงออบเจ็กต์หลังจากฟังก์ชันเรียกใช้ฟังก์ชันจะเรียกใช้ UB นั่นหมายความว่าการเข้าถึงดังกล่าวสามารถให้ค่าที่อ็อบเจ็กต์ถือไว้ก่อนที่จะถูก "เคลียร์" นั่นไม่ตรงข้ามกับความปลอดภัยได้อย่างไร?
supercat

0

ควรจะเป็นไปได้ที่จะเขียนฟังก์ชันเวอร์ชันพกพาโดยใช้วัตถุที่ระเหยได้ทางด้านขวามือและบังคับให้คอมไพเลอร์เก็บที่เก็บไว้ในอาร์เรย์

void volatileZeroMemory(void* ptr, unsigned long long size)
{
    volatile unsigned char zero = 0;
    unsigned char* bytePtr = static_cast<unsigned char*>(ptr);

    while (size--)
    {
        *bytePtr++ = zero;
    }

    zero = static_cast<unsigned char*>(ptr)[zero];
}

zeroวัตถุมีการประกาศvolatileที่ช่วยให้คอมไพเลอร์สามารถทำให้สมมติฐานเกี่ยวกับคุณค่าของมันไม่ถึงแม้ว่ามันมักจะประเมินเป็นศูนย์

นิพจน์การกำหนดขั้นสุดท้ายอ่านจากดัชนีระเหยในอาร์เรย์และเก็บค่าไว้ในวัตถุที่ระเหยได้ เนื่องจากการอ่านนี้ไม่สามารถปรับให้เหมาะสมได้จึงทำให้มั่นใจได้ว่าคอมไพลเลอร์ต้องสร้างร้านค้าที่ระบุไว้ในลูป


1
สิ่งนี้ใช้ไม่ได้เลย ... เพียงแค่ดูรหัสที่กำลังสร้างขึ้น
cooky451

1
เมื่ออ่าน ASM mo 'ที่สร้างขึ้นของฉันดีขึ้นดูเหมือนว่าจะอินไลน์การเรียกใช้ฟังก์ชันและคงการวนซ้ำ แต่ไม่ได้ทำการจัดเก็บใด ๆ*ptrในระหว่างการวนซ้ำนั้นหรือจริงๆแล้วอะไรเลย ... wtf ไปที่สมองของฉัน
underscore_d

3
@underscore_d เป็นเพราะมันเพิ่มประสิทธิภาพให้กับร้านค้าในขณะที่รักษาการอ่านค่าระเหย
D Krueger

1
ใช่และมันก็ทิ้งผลลัพธ์ให้ไม่เปลี่ยนแปลงedx: ฉันได้รับสิ่งนี้:.L16: subq $1, %rax; movzbl -1(%rsp), %edx; jne .L16
underscore_d

1
ถ้าฉันเปลี่ยนฟังก์ชั่นเพื่ออนุญาตให้ส่งผ่านvolatile unsigned char constไบต์เติมโดยพลการ... มันจะไม่อ่านด้วยซ้ำ โทร inlined สร้างขึ้นเพื่อเป็นเพียงvolatileFill() [load RAX with sizeof] .L9: subq $1, %rax; jne .L9เหตุใดเครื่องมือเพิ่มประสิทธิภาพ (A) จึงไม่อ่านไบต์เติมซ้ำและ (B) รบกวนการรักษาลูปโดยที่มันไม่ทำอะไรเลย
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.