กำลังส่งการอ้างอิงฟังก์ชันที่สร้างตัวชี้ที่ไม่ถูกต้อง


9

ฉันกำลังติดตามข้อผิดพลาดในรหัสบุคคลที่สามและฉัน จำกัด ให้แคบลงเหลือบางอย่างตามลำดับ

use libc::c_void;

pub unsafe fn foo() {}

fn main() {
    let ptr = &foo as *const _ as *const c_void;
    println!("{:x}", ptr as usize);
}

วิ่งบนเสถียร 1.38.0 นี้จะพิมพ์ตัวชี้ฟังก์ชั่น แต่เบต้า (1.39.0-beta.6) และส่งคืน '1' ทุกคืน ( สนามเด็กเล่น )

_การอนุมานคืออะไรและทำไมพฤติกรรมจึงเปลี่ยนไป

ฉันถือว่าวิธีที่ถูกต้องในการส่งแบบนี้จะเป็นไปได้foo as *const c_voidแต่นี่ไม่ใช่รหัสของฉัน


ฉันไม่สามารถตอบ "ทำไมถึงมีการเปลี่ยนแปลง" แต่ฉันเห็นด้วยกับคุณว่ารหัสไม่ถูกต้องในการเริ่มต้นด้วย fooเป็นตัวชี้ฟังก์ชันอยู่แล้วดังนั้นคุณไม่ควรใช้ที่อยู่ ที่สร้างการอ้างอิงสองครั้งดูเหมือนจะเป็นประเภทขนาดศูนย์ (เช่นค่ามายากล1)
Shepmaster

สิ่งนี้ไม่ได้ตอบคำถามของคุณอย่างแน่นอน แต่คุณอาจต้องการ:let ptr = foo as *const fn() as *const c_void;
Peter Hall

คำตอบ:


3

คำตอบนี้ขึ้นอยู่กับการตอบกลับในรายงานข้อผิดพลาดที่เกิดจากคำถามนี้

แต่ละฟังก์ชั่นใน Rust มีประเภทรายการฟังก์ชั่นซึ่งแตกต่างจากประเภทรายการฟังก์ชั่นของฟังก์ชั่นอื่น ๆ ด้วยเหตุผลนี้อินสแตนซ์ของประเภทรายการฟังก์ชั่นจึงไม่จำเป็นต้องเก็บข้อมูลใด ๆ เลย - ฟังก์ชั่นใดที่มันชี้ไปที่ชัดเจนจากประเภทของมัน ดังนั้นตัวแปร x ใน

let x = foo;

เป็นตัวแปรขนาด 0

ประเภทรายการฟังก์ชั่นบังคับโดยนัยกับประเภทตัวชี้ฟังก์ชั่นในกรณีที่จำเป็น ตัวแปร

let x: fn() = foo;

เป็นตัวชี้ทั่วไปของฟังก์ชันใด ๆ ที่มีลายเซ็นfn()ดังนั้นจึงจำเป็นต้องจัดเก็บตัวชี้ไปยังฟังก์ชันที่ชี้ไปที่จริงดังนั้นขนาดของxคือขนาดของตัวชี้

หากคุณใช้ที่อยู่ของฟังก์ชัน&fooคุณจะรับที่อยู่ของค่าชั่วคราวขนาดศูนย์ ก่อนหน้านี้ส่งมอบให้กับrustrepo ขมับขนาดศูนย์ใช้เพื่อสร้างการจัดสรรบนสแต็กและ&fooส่งคืนที่อยู่ของการจัดสรรนั้น ตั้งแต่การคอมมิชชันประเภท zero-size จะไม่สร้างการจัดสรรอีกต่อไปและใช้ magic address 1 แทนซึ่งจะอธิบายความแตกต่างระหว่างรุ่น Rust ที่แตกต่าง


มันสมเหตุสมผลแล้ว แต่ฉันก็ไม่มั่นใจว่ามันเป็นพฤติกรรมที่ต้องการโดยทั่วไปเพราะมันถูกสร้างขึ้นบนสมมติฐานที่ล่อแหลม ภายในรหัส Rust ที่ปลอดภัยไม่มีเหตุผลที่จะแยกแยะพอยน์เตอร์กับค่าของ ZST - เนื่องจากมีค่าที่เป็นไปได้เพียงค่าเดียวซึ่งเป็นที่รู้จักในเวลารวบรวม สิ่งนี้จะพังลงเมื่อคุณจำเป็นต้องใช้ค่า ZST นอกระบบประเภทสนิมเช่นที่นี่ มีแนวโน้มว่าจะมีผลเฉพาะกับfnประเภทรายการและการปิดไม่ใช่การจับภาพและสำหรับผู้ที่มีวิธีแก้ปัญหาเช่นเดียวกับในคำตอบของฉัน แต่ก็ยังคงเป็นปืนเท้า!
ปีเตอร์ฮอลล์

ตกลงฉันไม่ได้อ่านคำตอบที่ใหม่กว่าในปัญหา Github ฉันจะได้รับ segfault ด้วยรหัสนั้น แต่ถ้ารหัสอาจทำให้เกิด segfault ฉันก็เดาว่าพฤติกรรมใหม่ก็โอเค
ปีเตอร์ฮอลล์

คำตอบที่ดี @ PeterHall ฉันคิดแบบเดียวกันและฉันก็ยังไม่ได้ 100% ในเรื่องนี้ แต่อย่างน้อยสำหรับ temporaries และตัวแปร stack อื่น ๆ ไม่น่าจะมีปัญหากับการใส่ค่าขนาดศูนย์ทั้งหมดที่ 0x1 เพราะคอมไพเลอร์ทำให้ไม่มี รับประกันเกี่ยวกับรูปแบบสแต็กและคุณไม่สามารถรับประกันเอกลักษณ์ของพอยน์เตอร์ไปยัง ZST ได้ ซึ่งแตกต่างจากการพูดหล่อ*const i32ไป*const c_voidซึ่งความเข้าใจของเรายังคงรับประกันว่าจะรักษาตัวตนของตัวชี้
trentcl

2

_การอนุมานคืออะไรและทำไมพฤติกรรมจึงเปลี่ยนไป

แต่ละครั้งที่คุณทำการส่งตัวชี้แบบดิบคุณสามารถเปลี่ยนข้อมูลหนึ่งชิ้นเท่านั้น (การอ้างอิงหรือตัวชี้แบบดิบความไม่แน่นอนประเภท) ดังนั้นถ้าคุณทำแบบนี้:

let ptr = &foo as *const _

เมื่อคุณได้เปลี่ยนจากการอ้างอิงถึงตัวชี้ดิบชนิดอนุมานสำหรับ_ ต้องไม่เปลี่ยนแปลงและดังนั้นจึงเป็นชนิดของซึ่งเป็นบางประเภทอธิบายไม่ได้สำหรับฟังก์ชั่นfoofoo

แทนที่จะทำเช่นนั้นคุณสามารถส่งไปยังตัวชี้ฟังก์ชันโดยตรงซึ่งสามารถแสดงได้ในไวยากรณ์ของ Rust:

let ptr = foo as *const fn() as *const c_void;

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


1
ขอบคุณฉันรายงานว่าgithub.com/rust-lang/rust/issues/65499
Maciej Goszczycki

@MaciejGoszczycki ขอบคุณสำหรับการรายงาน! การตอบกลับทำให้ฉันชัดเจนขึ้น - ฉันจะโพสต์คำตอบตามคำตอบที่นั่น
Sven Marnach
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.