เท่าที่ C มาตรฐานเป็นห่วงถ้าคุณโยนตัวชี้ฟังก์ชั่นที่จะเป็นตัวชี้การทำงานของประเภทที่แตกต่างกันแล้วเรียกว่ามันเป็นพฤติกรรมที่ไม่ได้กำหนด ดูภาคผนวก J.2 (ข้อมูล):
พฤติกรรมไม่ได้กำหนดไว้ในสถานการณ์ต่อไปนี้:
- ตัวชี้ใช้เพื่อเรียกใช้ฟังก์ชันที่ประเภทไม่เข้ากันกับประเภทชี้ไปที่ (6.3.2.3)
ส่วน 6.3.2.3 วรรค 8 อ่าน:
ตัวชี้ไปยังฟังก์ชันประเภทหนึ่งอาจถูกแปลงเป็นตัวชี้ไปยังฟังก์ชันประเภทอื่นและกลับมาอีกครั้ง ผลลัพธ์จะเปรียบเทียบเท่ากับตัวชี้เดิม หากใช้ตัวชี้ที่แปลงแล้วเพื่อเรียกใช้ฟังก์ชันที่ชนิดไม่เข้ากันได้กับชนิดชี้ไปที่ลักษณะการทำงานจะไม่ได้กำหนดไว้
กล่าวอีกนัยหนึ่งคือคุณสามารถส่งตัวชี้ฟังก์ชันไปยังประเภทตัวชี้ฟังก์ชันอื่นส่งกลับอีกครั้งแล้วเรียกสิ่งนั้นและสิ่งต่างๆจะทำงานได้
คำจำกัดความของความเข้ากันได้ค่อนข้างซับซ้อน สามารถพบได้ในหัวข้อ 6.7.5.3 ย่อหน้าที่ 15:
สำหรับสองประเภทฟังก์ชั่นจะเข้ากันได้ทั้งสองจะต้องระบุประเภทผลตอบแทนที่เข้ากันได้127
ยิ่งไปกว่านั้นรายการประเภทพารามิเตอร์หากมีทั้งสองรายการจะต้องตกลงกันในจำนวนพารามิเตอร์และในการใช้จุดไข่ปลา พารามิเตอร์ที่เกี่ยวข้องต้องมีประเภทที่เข้ากันได้ หากประเภทหนึ่งมีรายการประเภทพารามิเตอร์และอีกประเภทหนึ่งถูกระบุโดยตัวประกาศฟังก์ชันที่ไม่ได้เป็นส่วนหนึ่งของนิยามฟังก์ชันและมีรายการตัวระบุว่างรายการพารามิเตอร์จะต้องไม่มีจุดไข่ปลาและประเภทของแต่ละพารามิเตอร์จะต้อง เข้ากันได้กับประเภทที่เป็นผลมาจากการใช้โปรโมชั่นอาร์กิวเมนต์เริ่มต้น หากประเภทหนึ่งมีรายการประเภทพารามิเตอร์และประเภทอื่นถูกระบุโดยนิยามฟังก์ชันที่มีรายการตัวระบุ (อาจว่างเปล่า) ทั้งสองจะเห็นด้วยในจำนวนพารามิเตอร์ และประเภทของพารามิเตอร์ต้นแบบแต่ละตัวจะต้องเข้ากันได้กับประเภทที่เป็นผลมาจากการประยุกต์ใช้การส่งเสริมอาร์กิวเมนต์เริ่มต้นกับประเภทของตัวระบุที่เกี่ยวข้อง (ในการพิจารณาความเข้ากันได้ของประเภทและประเภทคอมโพสิตแต่ละพารามิเตอร์ที่ประกาศด้วยฟังก์ชันหรือประเภทอาร์เรย์จะถือว่ามีประเภทที่ปรับปรุงแล้วและแต่ละพารามิเตอร์ที่ประกาศด้วยประเภทที่ผ่านการรับรองจะถือว่ามีเวอร์ชันที่ประกาศไว้อย่างไม่มีเงื่อนไข)
127) ถ้าฟังก์ชันทั้งสองประเภทเป็น '' แบบเก่า '' จะไม่มีการเปรียบเทียบประเภทพารามิเตอร์
กฎระเบียบในการพิจารณาว่าทั้งสองประเภทจะเข้ากันได้อธิบายไว้ในส่วน 6.2.7 และฉันจะไม่พูดกับพวกเขาที่นี่ตั้งแต่พวกเขากำลังค่อนข้างยาว แต่คุณสามารถอ่านได้บนร่างมาตรฐาน C99 นี้ (PDF)
กฎที่เกี่ยวข้องอยู่ในหัวข้อ 6.7.5.1 ย่อหน้าที่ 2:
เพื่อให้ตัวชี้สองชนิดเข้ากันได้ทั้งสองชนิดจะต้องมีคุณสมบัติเหมือนกันและทั้งสองชนิดจะต้องเป็นตัวชี้ชนิดที่เข้ากันได้
ดังนั้นเนื่องจาก a void*
เข้ากันไม่ได้กับ a struct my_struct*
ตัวชี้ฟังก์ชันของประเภทvoid (*)(void*)
จึงไม่สามารถทำงานร่วมกับตัวชี้ฟังก์ชันของประเภทvoid (*)(struct my_struct*)
ได้ดังนั้นการคัดเลือกตัวชี้ฟังก์ชันนี้จึงเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในทางเทคนิค
อย่างไรก็ตามในทางปฏิบัติคุณสามารถหลีกเลี่ยงตัวชี้ฟังก์ชันการหล่อได้อย่างปลอดภัยในบางกรณี ในรูปแบบการเรียกใช้ x86 อาร์กิวเมนต์จะถูกพุชบนสแต็กและตัวชี้ทั้งหมดมีขนาดเท่ากัน (4 ไบต์ใน x86 หรือ 8 ไบต์ใน x86_64) การเรียกใช้ตัวชี้ฟังก์ชันจะลดลงเพื่อผลักอาร์กิวเมนต์บนสแต็กและทำการข้ามไปยังเป้าหมายตัวชี้ฟังก์ชันทางอ้อมและเห็นได้ชัดว่าไม่มีความคิดเกี่ยวกับประเภทที่ระดับรหัสเครื่อง
สิ่งที่คุณทำไม่ได้แน่นอน:
- ส่งระหว่างตัวชี้ฟังก์ชันของรูปแบบการเรียกที่แตกต่างกัน คุณจะทำให้สแต็คยุ่งเหยิงและอย่างดีที่สุดก็ผิดพลาดที่แย่ที่สุดประสบความสำเร็จอย่างเงียบ ๆ ด้วยช่องโหว่ด้านความปลอดภัยขนาดใหญ่ ในการเขียนโปรแกรม Windows คุณมักจะส่งตัวชี้ฟังก์ชันไปรอบ ๆ Win32 คาดว่าทุกฟังก์ชั่นการโทรกลับจะใช้
stdcall
เรียกประชุม (ซึ่งมาโครCALLBACK
, PASCAL
และWINAPI
ทั้งหมดขยายตัวออกไป) หากคุณส่งตัวชี้ฟังก์ชันที่ใช้รูปแบบการเรียก C มาตรฐาน ( cdecl
) ความไม่ดีจะส่งผลให้
- ใน C ++ ให้ส่งระหว่างตัวชี้ฟังก์ชันสมาชิกคลาสและตัวชี้ฟังก์ชันปกติ สิ่งนี้มักจะเดินทางไปยังมือใหม่ C ++ ฟังก์ชันสมาชิกคลาสมี
this
พารามิเตอร์ที่ซ่อนอยู่และหากคุณส่งฟังก์ชันสมาชิกไปยังฟังก์ชันปกติจะไม่มีthis
วัตถุใดที่จะใช้และอีกครั้งจะส่งผลให้เกิดความเสียหายอย่างมาก
ความคิดที่ไม่ดีอีกอย่างที่บางครั้งอาจใช้ได้ผล แต่ก็เป็นพฤติกรรมที่ไม่ได้กำหนดไว้ด้วย:
- การแคสต์ระหว่างพอยน์เตอร์ฟังก์ชันและพอยน์เตอร์ปกติ (เช่นการร่าย a
void (*)(void)
ถึง a void*
) พอยน์เตอร์ของฟังก์ชันไม่จำเป็นต้องมีขนาดเท่ากับพอยน์เตอร์ทั่วไปเนื่องจากในบางสถาปัตยกรรมอาจมีข้อมูลบริบทเพิ่มเติม สิ่งนี้อาจใช้ได้กับ x86 แต่จำไว้ว่ามันเป็นพฤติกรรมที่ไม่ได้กำหนด