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


9

ฉันรู้ว่านี่เป็นเรื่องที่ค่อนข้างทั่วไป แต่เท่าที่ UB ทั่วไปหาได้ง่ายฉันไม่พบตัวแปรนี้จนถึงตอนนี้

ดังนั้นฉันจึงพยายามแนะนำวัตถุพิกเซลอย่างเป็นทางการในขณะที่หลีกเลี่ยงการคัดลอกข้อมูลจริง

ถูกต้องหรือไม่

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

รูปแบบการใช้ที่คาดไว้ง่ายขึ้นมาก:

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

โดยเฉพาะอย่างยิ่ง:

  • รหัสนี้มีพฤติกรรมที่กำหนดไว้อย่างดีหรือไม่?
  • ถ้าใช่มันปลอดภัยที่จะใช้ตัวชี้ที่ส่งคืนหรือไม่?
  • ถ้าใช่Pixelมันสามารถขยายประเภทอื่นได้หรือไม่? (คลายข้อ จำกัด ของ is_trivial? pixel ด้วย 3 องค์ประกอบเท่านั้น)

เสียงดังกราวและจีซีซีทำให้ทั้งวงนั้นกลายเป็นความว่างเปล่าซึ่งเป็นสิ่งที่ฉันต้องการ ตอนนี้ฉันอยากรู้ว่านี่เป็นการละเมิดกฎ C ++ หรือไม่

ลิงค์ Godboltหากคุณต้องการเล่นกับมัน

(หมายเหตุ: ฉันไม่ได้ติดแท็ก c ++ 17 แม้ว่าจะstd::byteเป็นเพราะคำถามยังคงใช้อยู่char)


2
แต่Pixels ที่อยู่ติดกันใหม่ยังคงไม่ได้เป็นอาร์เรย์ของPixels
Jarod42

1
@spectras ที่ไม่ได้สร้างอาร์เรย์ว่า คุณเพียงแค่มีวัตถุพิกเซลจำนวนมากอยู่ติดกัน มันแตกต่างจากอาเรย์
NathanOliver

1
ดังนั้นไม่คุณจะทำอย่างไรที่pixels[some_index]หรือ*(pixels + something)? นั่นจะเป็น UB
NathanOliver

1
ส่วนที่เกี่ยวข้องคือที่นี่และวลีที่สำคัญคือถ้าจุด P เพื่อองค์ประกอบอาร์เรย์ฉันของวัตถุ x ที่นี่pixels(P) ไม่ได้เป็นตัวชี้ไปยังวัตถุอาร์เรย์ Pixelแต่ชี้ให้เป็นหนึ่งเดียว นั่นหมายความว่าคุณสามารถเข้าถึงได้pixels[0]ตามกฎหมายเท่านั้น
NathanOliver

3
คุณต้องการที่จะอ่านwg21.link/P0593
ecatmur

คำตอบ:


3

มันเป็นพฤติกรรมที่ไม่ได้กำหนดให้ใช้ผลลัพธ์ของpromoteการเป็นอาร์เรย์ ถ้าเราดู[expr.add] /4.2เรามี

มิฉะนั้นถ้าPชี้ไปที่องค์ประกอบอาเรย์iของวัตถุอาเรย์ที่xมีnองค์ประกอบ ([dcl.array]) นิพจน์P + JและJ + P(โดยที่Jมีค่าj) ชี้ไปที่องค์ประกอบอาเรย์ (อาจเป็นไปตามสมมุติฐาน) i+jของxif 0≤i+j≤nและนิพจน์P - Jชี้ไปที่ ( อาจ-สมมุติ) องค์ประกอบอาร์เรย์ i−jของถ้าx0≤i−j≤n

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


ขอบคุณสำหรับการค้นหาอย่างรวดเร็ว ฉันจะทำซ้ำแทนฉันเดา ในฐานะที่เป็น sidenote นี่ก็หมายความว่า&somevector[0] + 1เป็น UB (ดีฉันหมายถึงการใช้ตัวชี้ผลลัพธ์จะเป็น)
สเปกตรัม

@spectras ไม่เป็นไร คุณสามารถดึงตัวชี้ไปยังวัตถุหนึ่งที่ผ่านมาได้ คุณไม่สามารถอ่านค่าตัวชี้นั้นได้แม้ว่าจะมีวัตถุที่ถูกต้องอยู่ก็ตาม
NathanOliver

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

@sprasras ไม่มีปัญหา ส่วนนี้ของ C ++ อาจเป็นเรื่องยากมาก แม้ว่าฮาร์ดแวร์จะทำสิ่งที่เราต้องการให้ทำ แต่นั่นไม่ใช่สิ่งที่เป็นรหัส เรากำลังเขียนโค้ดไปยังเครื่องนามธรรม C ++ และมันเป็นเครื่องจักรที่ติดตัวไปด้วย) หวังว่า P0593 จะได้รับการรับรองและสิ่งนี้จะกลายเป็นเรื่องง่ายขึ้นมาก
NathanOliver

1
@spectras ไม่เนื่องจากเวกเตอร์ std ถูกกำหนดว่ามีอาเรย์และคุณสามารถทำการคำนวณทางคณิตศาสตร์ระหว่างอิลิเมนต์อาร์เรย์ ไม่มีทางที่จะนำเวกเตอร์มาตรฐานไปใช้ใน C ++ เองเศร้าโดยไม่ต้องทำงานใน UB
Yakk - Adam Nevraumont

1

คุณมีคำตอบเกี่ยวกับการใช้งานตัวชี้ที่ส่งคืนอย่าง จำกัด แต่ฉันต้องการเพิ่มว่าฉันคิดว่าคุณต้องstd::launderสามารถเข้าถึงตัวแรกได้Pixel:

reinterpret_castจะทำก่อนที่Pixelวัตถุถูกสร้างขึ้น (สมมติว่าคุณไม่ได้ทำดังนั้นในgetSomeImageData) ดังนั้นreinterpret_castจะไม่เปลี่ยนค่าตัวชี้ ตัวชี้ผลลัพธ์จะยังคงชี้ไปที่องค์ประกอบแรกของstd::byteอาร์เรย์ที่ส่งผ่านไปยังฟังก์ชัน

เมื่อคุณสร้างPixelวัตถุพวกมันจะถูกซ้อนกันภายในstd::byteอาเรย์และstd::byteอาเรย์จะจัดให้มีการจัดเก็บสำหรับPixelวัตถุ

มีหลายกรณีที่การใช้ที่เก็บข้อมูลซ้ำทำให้ตัวชี้ไปยังวัตถุเก่าชี้ไปที่วัตถุใหม่โดยอัตโนมัติ แต่นี่ไม่ใช่สิ่งที่เกิดขึ้นที่นี่ดังนั้นresultจะยังคงชี้ไปที่std::byteวัตถุไม่ใช่Pixelวัตถุ ฉันเดาว่าใช้มันราวกับว่ามันชี้ไปที่Pixelวัตถุนั้นในทางเทคนิคจะเป็นพฤติกรรมที่ไม่ได้กำหนด

ผมคิดว่านี้ยังคงถือแม้ว่าคุณจะทำreinterpret_castหลังจากการสร้างPixelวัตถุตั้งแต่Pixelวัตถุและstd::byteที่ให้การจัดเก็บข้อมูลสำหรับมันไม่ได้เป็นตัวชี้ interconvertible ดังนั้นแม้กระทั่งตัวชี้จะยังคงชี้ไปstd::byteที่Pixelวัตถุไม่ใช่

หากคุณได้รับตัวชี้ให้ส่งคืนจากผลลัพธ์ของการวางตำแหน่งใหม่อย่างใดอย่างหนึ่งทุกอย่างควรจะโอเคจนถึงการเข้าถึงPixelวัตถุเฉพาะนั้นที่เกี่ยวข้อง


นอกจากนี้คุณต้องตรวจสอบให้แน่ใจว่าstd::byteตัวชี้ถูกจัดตำแหน่งอย่างเหมาะสมPixelและอาร์เรย์มีขนาดใหญ่พอจริง เท่าที่ฉันจำได้ว่ามาตรฐานไม่จำเป็นต้องPixelมีการจัดตำแหน่งแบบเดียวกันstd::byteหรือว่ามันไม่มีช่องว่างภายใน


นอกจากนี้ยังไม่มีสิ่งนี้ขึ้นอยู่กับPixelความสำคัญหรือคุณสมบัติอื่น ๆ ของมัน ทุกอย่างจะทำงานในลักษณะเดียวกันตราบใดที่std::byteอาร์เรย์มีขนาดเพียงพอและจัดแนวให้เหมาะสมกับPixelวัตถุ


ฉันเชื่อว่าถูกต้อง แม้ว่าสิ่งที่อาร์เรย์ (unimplementability ของstd::vector) คือไม่ได้เป็นปัญหาคุณยังจะต้องstd::launderผลที่ตามมาก่อนที่จะเข้าถึงใด ๆ ของตำแหน่งจะnewเอ็ดPixels ณ ตอนนี้std::launderที่นี่คือ UB เนื่องจากPixels ที่อยู่ติดกันนั้นสามารถเข้าถึงได้จากตัวชี้การซัก
Fureeish

@Fureeish ฉันไม่แน่ใจว่าทำไมstd::launderจะเป็น UB ถ้าใช้กับresultก่อนกลับมา ที่อยู่ติดกันPixelไม่ได้ " เข้าถึง " ผ่านตัวชี้ซักที่เกิดจากความเข้าใจของฉันeel.is/c++draft/ptr.launder#4 และถึงแม้ฉันจะไม่เห็นว่ามันเป็น UB ได้อย่างไรเพราะทั้งstd::byteอาร์เรย์ดั้งเดิมสามารถเข้าถึงได้จากตัวชี้ดั้งเดิม
วอลนัท

แต่ตัวถัดไปPixelจะไม่สามารถเข้าถึงได้จากstd::byteตัวชี้ แต่มาจากlaunderตัวชี้ ed ฉันเชื่อว่านี่เกี่ยวข้องกับที่นี่ ฉันดีใจที่ได้รับการแก้ไข
Fureeish

@Fureeish จากสิ่งที่ฉันสามารถบอกได้ว่าไม่มีตัวอย่างใดให้ใช้ที่นี่และคำจำกัดความของข้อกำหนดยังบอกว่าเหมือนกับมาตรฐาน ความสามารถเข้าถึงได้ถูกกำหนดในรูปแบบของหน่วยเก็บข้อมูลไบต์ไม่ใช่วัตถุ ไบต์ที่ครอบครองโดยถัดไปPixelดูเหมือนว่าฉันสามารถเข้าถึงได้จากตัวชี้ดั้งเดิมเนื่องจากตัวชี้ดั้งเดิมชี้ไปที่องค์ประกอบของstd::byteอาร์เรย์ซึ่งมีไบต์ที่ประกอบขึ้นเป็นที่เก็บสำหรับการPixelสร้าง " หรือภายในอาร์เรย์ที่ล้อมรอบทันทีซึ่ง Z คือ องค์ประกอบ "ใช้เงื่อนไข (โดยที่ZคือYคือstd::byteองค์ประกอบของตัวเอง)
วอลนัท

ฉันคิดว่าไบต์ของพื้นที่จัดเก็บข้อมูลที่Pixelครอบครองต่อไปไม่สามารถเข้าถึงได้ผ่านตัวชี้การซักฟอกเพราะPixelวัตถุชี้ไปที่ไม่ได้เป็นองค์ประกอบของวัตถุอาร์เรย์และยังไม่ได้ชี้ interconvertible กับวัตถุที่เกี่ยวข้องอื่น ๆ แต่ฉันก็กำลังคิดถึงรายละเอียดนี้std::launderเป็นครั้งแรกในส่วนลึกนั้น ฉันไม่แน่ใจ 100% เกี่ยวกับเรื่องนี้เช่นกัน
วอลนัท
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.