เหตุใดตัวชี้ฟังก์ชันและตัวชี้ข้อมูลจึงไม่เข้ากันใน C / C ++


130

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


16
ไม่ได้กำหนดไว้ในมาตรฐาน C ซึ่งกำหนดไว้ใน POSIX คำนึงถึงความแตกต่าง
ephemient

ฉันเป็นคนใหม่เล็กน้อยสำหรับเรื่องนี้ แต่คุณไม่ควรแคสต์ทางด้านขวาของ "="? สำหรับฉันดูเหมือนว่าปัญหาคือคุณกำลังกำหนดให้กับตัวชี้โมฆะ แต่ฉันเห็นว่าเพจคนทำเช่นนี้หวังว่าจะมีคนให้ความรู้ฉันได้ ฉันเห็นตัวอย่างใน 'net of people ที่ส่งผลตอบแทนจาก dlsym เช่นที่นี่: daniweb.com/forums/thread62561.html
JasonWoof

9
สังเกตสิ่งที่ POSIX พูดในส่วนประเภทข้อมูล : §2.12.3ประเภทตัวชี้ voidทุกประเภทชี้ทำงานจะต้องมีการแสดงเช่นเดียวกับตัวชี้ประเภทที่จะ การแปลงตัวชี้ฟังก์ชันเป็นจะvoid *ต้องไม่เปลี่ยนการแทนค่า void *คุ้มค่าที่เกิดจากการแปลงดังกล่าวสามารถแปลงกลับไปที่เดิมประเภทตัวชี้ฟังก์ชั่นที่ใช้หล่ออย่างชัดเจนโดยไม่ต้องสูญเสียข้อมูล หมายเหตุ : มาตรฐาน ISO C ไม่ต้องการสิ่งนี้ แต่จำเป็นสำหรับความสอดคล้องของ POSIX
Jonathan Leffler

2
นี่คือคำถามในส่วน ABOUT ของเว็บไซต์นี้ .. :) :) พบกันที่นี่
ZooZ

1
@KeithThompson: โลกเปลี่ยน - และ POSIX ก็ทำเช่นกัน สิ่งที่ฉันเขียนในปี 2012 ใช้ไม่ได้อีกต่อไปในปี 2018 มาตรฐาน POSIX เปลี่ยนคำฟุ่มเฟือย ตอนนี้เชื่อมโยงกับdlsym()- สังเกตส่วนท้ายของส่วน 'การใช้งานแอปพลิเคชัน' ที่ระบุว่า: โปรดทราบว่าการแปลงจากvoid *ตัวชี้เป็นตัวชี้ฟังก์ชันเช่นเดียวกับ: fptr = (int (*)(int))dlsym(handle, "my_function"); ไม่ได้กำหนดโดยมาตรฐาน ISO C มาตรฐานนี้กำหนดให้การแปลงนี้ทำงานได้อย่างถูกต้องในการปฏิบัติตามการนำไปใช้
Jonathan Leffler

คำตอบ:


171

สถาปัตยกรรมไม่จำเป็นต้องจัดเก็บรหัสและข้อมูลไว้ในหน่วยความจำเดียวกัน ด้วยสถาปัตยกรรม Harvard รหัสและข้อมูลจะถูกเก็บไว้ในหน่วยความจำที่แตกต่างกันอย่างสิ้นเชิง สถาปัตยกรรมส่วนใหญ่เป็นสถาปัตยกรรม Von Neumann ที่มีรหัสและข้อมูลอยู่ในหน่วยความจำเดียวกัน แต่ C ไม่ได้ จำกัด ตัวเองไว้ที่สถาปัตยกรรมบางประเภทเท่านั้นหากเป็นไปได้


15
นอกจากนี้แม้ว่ารหัสและข้อมูลจะถูกเก็บไว้ในที่เดียวกันในฮาร์ดแวร์ทางกายภาพซอฟต์แวร์และการเข้าถึงหน่วยความจำมักจะป้องกันไม่ให้ข้อมูลที่รันเป็นรหัสโดยไม่ได้รับการอนุมัติจากระบบปฏิบัติการ DEP และอื่น ๆ
Michael Graczyk

15
อย่างน้อยก็สำคัญพอ ๆ กับการมีช่องว่างที่อยู่ที่แตกต่างกัน (อาจสำคัญกว่า) คือตัวชี้ฟังก์ชันอาจมีการแทนค่าที่แตกต่างจากตัวชี้ข้อมูล
Michael Burr

14
คุณไม่จำเป็นต้องมีสถาปัตยกรรม Harvard เพื่อให้มีรหัสและตัวชี้ข้อมูลโดยใช้ช่องว่างที่อยู่ที่แตกต่างกัน - รุ่นหน่วยความจำ "Small" ของ DOS รุ่นเก่าก็ทำเช่นนี้ (ใกล้ตัวชี้ด้วยCS != DS)
คาเฟ่

1
แม้แต่โปรเซสเซอร์สมัยใหม่ก็ยังต้องดิ้นรนกับส่วนผสมเช่นคำสั่งและแคชข้อมูลโดยทั่วไปจะจัดการแยกกันแม้ว่าระบบปฏิบัติการจะอนุญาตให้คุณเขียนโค้ดที่ใดที่หนึ่งก็ตาม
PypeBros

3
@EricJ จนกว่าคุณจะโทรVirtualProtectซึ่งช่วยให้คุณสามารถทำเครื่องหมายพื้นที่ของข้อมูลว่าปฏิบัติการได้
Dietrich Epp

37

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

ภาษาได้รับการออกแบบมาไม่เพียง แต่สำหรับแอปพลิเคชันเดสก์ท็อปในปัจจุบันเท่านั้น แต่ยังช่วยให้สามารถใช้งานได้กับฮาร์ดแวร์ชุดใหญ่


ดูเหมือนว่าคณะกรรมการภาษาซีไม่เคยตั้งใจvoid*ให้เป็นตัวชี้ในการทำงาน แต่พวกเขาต้องการเพียงแค่ตัวชี้ทั่วไปไปยังวัตถุ

เหตุผล C99 กล่าวว่า:

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

การใช้ void* (“ ตัวชี้ไปที่void”) เป็นประเภทตัวชี้วัตถุทั่วไปเป็นสิ่งประดิษฐ์ของคณะกรรมการ C89 การยอมรับประเภทนี้ได้รับการกระตุ้นจากความปรารถนาที่จะระบุอาร์กิวเมนต์ต้นแบบของฟังก์ชันที่แปลงพอยน์เตอร์ตามอำเภอใจอย่างเงียบ ๆ (เช่นเดียวกับในfread) หรือบ่นหากประเภทอาร์กิวเมนต์ไม่ตรงกันstrcmpทุกประการ(เหมือนใน) ไม่มีอะไรพูดเกี่ยวกับตัวชี้ไปยังฟังก์ชันซึ่งอาจไม่ตรงกับตัวชี้วัตถุและ / หรือจำนวนเต็ม

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


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

15
@CrazyEddie คุณไม่สามารถกำหนดตัวชี้ฟังก์ชันให้กับไฟล์void *.
ouah

4
ฉันอาจจะผิดที่เป็นโมฆะ * ยอมรับตัวชี้ฟังก์ชัน แต่ประเด็นยังคงอยู่ บิตคือบิต มาตรฐานอาจกำหนดให้ขนาดของประเภทต่างๆสามารถรองรับข้อมูลจากกันได้และการกำหนดจะได้รับการรับประกันว่าจะทำงานได้แม้ว่าจะใช้ในส่วนหน่วยความจำที่แตกต่างกันก็ตาม สาเหตุที่ความไม่ลงรอยกันนี้มีอยู่ก็คือสิ่งนี้ไม่ได้รับการรับรองตามมาตรฐานดังนั้นข้อมูลอาจสูญหายไปในงานมอบหมาย
Edward Strange

5
แต่การต้องการsizeof(void*) == sizeof( void(*)() )จะทำให้สิ้นเปลืองพื้นที่ในกรณีที่ตัวชี้ฟังก์ชันและตัวชี้ข้อมูลมีขนาดต่างกัน นี่เป็นกรณีทั่วไปในยุค 80 เมื่อมีการเขียนมาตรฐาน C ตัวแรก
Robᵩ

8
@RichardChambers: ช่องว่างที่อยู่ที่แตกต่างกันอาจมีความกว้างของที่อยู่ที่แตกต่างกันเช่นAtmel AVRที่ใช้ 16 บิตสำหรับคำแนะนำและ 8 บิตสำหรับข้อมูล ในกรณีนั้นจะเป็นการยากที่จะแปลงจาก data (8 bit) ไปเป็น function (16 bit) pointers และกลับมาอีกครั้ง C ควรจะใช้งานง่าย ส่วนหนึ่งของความสะดวกนั้นมาจากการทิ้งข้อมูลและตัวชี้คำสั่งไม่เข้ากัน
John Bode

30

สำหรับผู้ที่จำ MS-DOS, Windows 3.1 ขึ้นไปคำตอบนั้นค่อนข้างง่าย สิ่งเหล่านี้ใช้เพื่อสนับสนุนหน่วยความจำรุ่นต่างๆโดยมีคุณสมบัติที่แตกต่างกันสำหรับรหัสและตัวชี้ข้อมูล

ตัวอย่างเช่นสำหรับรุ่น Compact (รหัสขนาดเล็กข้อมูลขนาดใหญ่):

sizeof(void *) > sizeof(void(*)())

และตรงกันข้ามในโมเดลขนาดกลาง (รหัสขนาดใหญ่ข้อมูลขนาดเล็ก):

sizeof(void *) < sizeof(void(*)())

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

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


Re: "การแปลงตัวชี้ฟังก์ชันเป็นตัวชี้ข้อมูลจะไม่ให้ตัวชี้ที่มีความสัมพันธ์กับฟังก์ชันนี้เลยและด้วยเหตุนี้จึงไม่มีประโยชน์สำหรับการแปลงดังกล่าว": สิ่งนี้ไม่เป็นไปตามทั้งหมด แปลงint*ไปvoid*ให้คุณตัวชี้ว่าคุณไม่สามารถจริงๆทำอะไรด้วย แต่มันก็ยังคงมีประโยชน์เพื่อให้สามารถดำเนินการแปลง (เนื่องจากvoid*สามารถจัดเก็บตัวชี้วัตถุใด ๆได้ดังนั้นจึงสามารถใช้กับอัลกอริทึมทั่วไปที่ไม่จำเป็นต้องรู้ว่าพวกเขาถือประเภทใดสิ่งเดียวกันนี้อาจมีประโยชน์สำหรับตัวชี้ฟังก์ชันเช่นกันหากได้รับอนุญาต)
ruakh

4
@ruakh: ในกรณีของการแปลงที่int *จะvoid *ที่void *มีการประกันเพื่ออย่างน้อยชี้ไปที่วัตถุเดียวกันเช่นเดิมint *ได้ - int n; memcpy(&n, src, sizeof n);ดังนั้นนี้จะเป็นประโยชน์สำหรับขั้นตอนวิธีการทั่วไปที่เข้าถึงชี้ไปยังวัตถุเช่น ในกรณีที่การแปลงตัวชี้ฟังก์ชันเป็น a void *ไม่ทำให้ตัวชี้ชี้ไปที่ฟังก์ชันนั้นจะไม่มีประโยชน์สำหรับอัลกอริทึมดังกล่าวสิ่งเดียวที่คุณทำได้คือแปลงvoid *กลับเป็นตัวชี้ฟังก์ชันอีกครั้งดังนั้นคุณอาจเป็น เพียงแค่ใช้ตัวชี้unionที่มีvoid *และฟังก์ชัน
คาเฟ่

@caf: พอใช้. ขอบคุณที่ชี้ให้เห็น และสำหรับเรื่องที่แม้ว่าvoid* ไม่ได้memcpyชี้ไปที่ฟังก์ชั่นที่ผมคิดว่ามันจะเป็นความคิดที่ไม่ดีสำหรับคนที่จะผ่านไป :-P
ruakh

คัดลอกจากด้านบน: สังเกตสิ่งที่ POSIX กล่าวในประเภทข้อมูล : §2.12.3ประเภทตัวชี้ voidทุกประเภทชี้ทำงานจะต้องมีการแสดงเช่นเดียวกับตัวชี้ประเภทที่จะ การแปลงตัวชี้ฟังก์ชันเป็นจะvoid *ต้องไม่เปลี่ยนการแทนค่า void *คุ้มค่าที่เกิดจากการแปลงดังกล่าวสามารถแปลงกลับไปที่เดิมประเภทตัวชี้ฟังก์ชั่นที่ใช้หล่ออย่างชัดเจนโดยไม่ต้องสูญเสียข้อมูล หมายเหตุ : มาตรฐาน ISO C ไม่ต้องการสิ่งนี้ แต่จำเป็นสำหรับความสอดคล้องของ POSIX
Jonathan Leffler

@caf หากควรส่งผ่านไปยังการโทรกลับบางส่วนที่รู้ประเภทที่เหมาะสมฉันสนใจเฉพาะความปลอดภัยแบบไปกลับเท่านั้นไม่ใช่ความสัมพันธ์อื่นใดที่อาจมีค่าที่แปลงแล้ว
Deduplicator

23

ตัวชี้เป็นโมฆะควรจะสามารถรองรับตัวชี้ไปยังข้อมูลประเภทใดก็ได้ - แต่ไม่จำเป็นต้องเป็นตัวชี้ไปยังฟังก์ชัน บางระบบมีข้อกำหนดสำหรับพอยน์เตอร์ไปยังฟังก์ชันที่แตกต่างกันมากกว่าตัวชี้ไปยังข้อมูล (เช่นมี DSP ที่มีการกำหนดแอดเดรสสำหรับข้อมูลเทียบกับโค้ดที่แตกต่างกันโมเดลขนาดกลางบน MS-DOS ใช้พอยน์เตอร์ 32 บิตสำหรับโค้ด แต่มีพอยน์เตอร์ 16 บิตสำหรับข้อมูลเท่านั้น) .


1
แต่ไม่ควรให้ฟังก์ชัน dlsym () ส่งคืนสิ่งอื่นที่ไม่ใช่โมฆะ * ฉันหมายความว่าถ้าโมฆะ * ไม่ใหญ่พอสำหรับตัวชี้ฟังก์ชันเราไม่ได้ fubared แล้วหรือ?
Manav

1
@Knickerkicker: ใช่น่าจะเป็น หากหน่วยความจำทำหน้าที่ประเภทการส่งคืนจาก dlsym จะถูกกล่าวถึงในระยะยาวซึ่งอาจเป็น 9 หรือ 10 ปีที่แล้วในรายการอีเมลของ OpenGroup ฉันจำไม่ได้ว่าอะไร (ถ้ามี) มาจากมัน
Jerry Coffin

1
คุณถูก. นี่เป็นบทสรุปที่ดีพอสมควร (แม้ว่าจะล้าสมัย) ในประเด็นของคุณ
Manav


2
@LegoStormtroopr: น่าสนใจว่า 21 คนเห็นด้วยกับแนวคิดเรื่องการโหวตขึ้นแค่ไหนแต่มีเพียง 3 คนเท่านั้นที่ทำเช่นนั้น :-)
Jerry Coffin

13

นอกเหนือจากสิ่งที่กล่าวไปแล้วที่นี่มันเป็นเรื่องน่าสนใจที่จะดู POSIX dlsym():

มาตรฐาน ISO C ไม่ได้กำหนดให้พอยน์เตอร์ไปยังฟังก์ชันสามารถเหวี่ยงกลับไปกลับมาเพื่อชี้ไปยังข้อมูลได้ อันที่จริงมาตรฐาน ISO C ไม่ได้กำหนดให้วัตถุประเภทโมฆะ * สามารถถือตัวชี้ไปที่ฟังก์ชันได้ อย่างไรก็ตามการใช้งานที่รองรับส่วนขยาย XSI นั้นต้องการให้อ็อบเจ็กต์ประเภท void * สามารถถือพอยน์เตอร์ไปยังฟังก์ชันได้ อย่างไรก็ตามผลลัพธ์ของการแปลงตัวชี้เป็นฟังก์ชันเป็นตัวชี้ไปยังชนิดข้อมูลอื่น (ยกเว้นโมฆะ *) ยังไม่ได้กำหนดไว้ โปรดทราบว่าคอมไพเลอร์ที่เป็นไปตามมาตรฐาน ISO C จำเป็นต้องสร้างคำเตือนหากมีการพยายามแปลงจากตัวชี้โมฆะ * เป็นตัวชี้ฟังก์ชันดังใน:

 fptr = (int (*)(int))dlsym(handle, "my_function");

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


นั่นหมายความว่าการใช้ dlsym เพื่อรับที่อยู่ของฟังก์ชันนั้นไม่ปลอดภัยหรือไม่? ปัจจุบันมีวิธีที่ปลอดภัยหรือไม่?
gexicide

4
หมายความว่าปัจจุบัน POSIX ต้องการจากแพลตฟอร์ม ABI ที่ทั้งฟังก์ชันและตัวชี้ข้อมูลสามารถส่งไปvoid*และกลับได้อย่างปลอดภัย
Maxim Egorushkin

@gexicide หมายความว่าการใช้งานที่สอดคล้องกับ POSIX ได้ขยายไปยังภาษาโดยให้ความหมายที่กำหนดในการนำไปใช้กับพฤติกรรมที่ไม่ได้กำหนดไว้ตามมาตรฐาน intself มันถูกระบุว่าเป็นหนึ่งในส่วนขยายทั่วไปของมาตรฐาน C99 ส่วน J.5.7 ตัวชี้ฟังก์ชันร่าย
David Hammen

1
@DavidHammen ไม่ใช่ส่วนขยายของภาษา แต่เป็นข้อกำหนดพิเศษใหม่ C ไม่จำเป็นต้องvoid*เข้ากันได้กับตัวชี้ฟังก์ชันในขณะที่ POSIX ทำได้
Maxim Egorushkin

9

C ++ 11 มีวิธีแก้ปัญหาความไม่ตรงกันในระยะยาวระหว่าง C / C ++ และ POSIX เกี่ยวกับdlsym(). สามารถใช้reinterpret_castเพื่อแปลงตัวชี้ฟังก์ชันเป็น / จากตัวชี้ข้อมูลได้ตราบเท่าที่การใช้งานรองรับคุณสมบัตินี้

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


หนึ่งสามารถ แต่ไม่ควร คอมไพเลอร์ที่สอดคล้องต้องสร้างคำเตือนสำหรับสิ่งนั้น (ซึ่งจะทำให้เกิดข้อผิดพลาด cf. -Werror) ดีกว่า (และไม่ใช่ UB) แก้ปัญหาคือการดึงตัวชี้ไปยังวัตถุที่ส่งกลับโดยdlsym(คือvoid**) และแปลงที่ให้เป็นตัวชี้ไปชี้ฟังก์ชัน ยังคงกำหนดการนำไปใช้ แต่ไม่ทำให้เกิดคำเตือน / ข้อผิดพลาดอีกต่อไป
Konrad Rudolph

3
@KonradRudolph: ไม่เห็นด้วย. ข้อความ "รองรับตามเงื่อนไข" ถูกเขียนขึ้นโดยเฉพาะเพื่ออนุญาตdlsymและGetProcAddressรวบรวมโดยไม่มีคำเตือน
MSalters

@MSalters คุณหมายถึงอะไร“ ไม่เห็นด้วย”? ไม่ว่าฉันจะถูกหรือผิด เอกสาร dlsym กล่าวอย่างชัดเจนว่า“คอมไพเลอร์ที่สอดคล้องกับมาตรฐาน ISO C จะต้องสร้างการเตือนถ้าแปลงจากตัวชี้โมฆะ * ตัวชี้ฟังก์ชั่นที่มีการพยายาม” สิ่งนี้ไม่เหลือที่ว่างสำหรับการเก็งกำไร และ GCC (with -pedantic) ไม่เตือน อีกครั้งไม่สามารถคาดเดาได้
Konrad Rudolph

1
การติดตามผล: ฉันคิดว่าตอนนี้ฉันเข้าใจแล้ว ไม่ใช่ยูบีนะ มีการกำหนดการนำไปใช้งาน ฉันยังไม่แน่ใจว่าต้องสร้างคำเตือนหรือไม่ - อาจไม่ใช่ โอ้ดี.
Konrad Rudolph

2
@KonradRudolph: ฉันไม่เห็นด้วยกับ "ไม่ควร" ของคุณซึ่งเป็นความเห็น คำตอบกล่าวถึง C ++ 11 โดยเฉพาะและฉันเป็นสมาชิกของ C ++ CWG ในขณะที่ปัญหาได้รับการแก้ไข C99 มีถ้อยคำที่แตกต่างกันการสนับสนุนตามเงื่อนไขคือสิ่งประดิษฐ์ C ++
MSalters

7

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


'ความแตกต่างทางกายภาพ' ฉันเข้าใจ แต่คุณสามารถอธิบายเพิ่มเติมเกี่ยวกับความแตกต่างที่ 'พื้นฐานที่เข้ากันไม่ได้' อย่างที่ฉันพูดในคำถามตัวชี้ที่เป็นโมฆะไม่ควรมีขนาดใหญ่เท่ากับตัวชี้ประเภทใด ๆ หรือเป็นข้อสันนิษฐานที่ไม่ถูกต้องในส่วนของฉัน
Manav

@KnickerKicker: void *มีขนาดใหญ่พอที่จะเก็บตัวชี้ข้อมูลใด ๆ แต่ไม่จำเป็นต้องมีตัวชี้ฟังก์ชันใด ๆ
ephemient

1
ย้อนกลับไปในอนาคต: P
SSpoke

5

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

ตัวอย่างเช่นอาจเป็นไปไม่ได้ในบางสถาปัตยกรรม - ไม่ได้กำหนดอนุญาตให้พวกเขายังคงมีไลบรารี 'C' ที่สอดคล้องกันแม้ว่าคุณจะไม่สามารถทำได้ก็ตาม


5

วิธีแก้ปัญหาอื่น:

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

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

วิธีนี้หลีกเลี่ยงการละเมิดกฎการใช้นามแฝงโดยดำเนินการchar []แทนซึ่งได้รับอนุญาตให้ใช้นามแฝงทุกประเภท

อีกวิธีหนึ่ง:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

แต่ฉันอยากจะแนะนำmemcpyแนวทางนี้หากคุณต้องการ C ที่ถูกต้อง 100%


5

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

ฉันเชื่อว่าอาจเป็นประเภทต่างๆได้เนื่องจากมาตรฐานไม่ต้องการ จำกัด การใช้งานที่เป็นไปได้ซึ่งจะช่วยประหยัดพื้นที่เมื่อไม่จำเป็นหรือเมื่อขนาดอาจทำให้ CPU ต้องใช้อึมากขึ้นเพื่อใช้งาน ฯลฯ ...


3

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

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

จากนั้นในแอปพลิเคชันของคุณ:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

อนึ่งนี่เป็นแนวทางปฏิบัติที่ดีในการออกแบบและทำให้ง่ายต่อการรองรับทั้งการโหลดแบบไดนามิกผ่านdlopenและการเชื่อมโยงแบบคงที่โมดูลทั้งหมดบนระบบที่ไม่รองรับการเชื่อมโยงแบบไดนามิกหรือในกรณีที่ผู้ใช้ / ผู้รวมระบบไม่ต้องการใช้การเชื่อมโยงแบบไดนามิก


2
ดี! แม้ว่าฉันยอมรับว่าสิ่งนี้ดูเหมือนจะบำรุงรักษาได้มากกว่า แต่ก็ยังไม่ชัดเจน (สำหรับฉัน) ว่าฉันใช้การเชื่อมโยงแบบคงที่ด้านบนของสิ่งนี้อย่างไร คุณสามารถอธิบายได้หรือไม่?
Manav

2
หากแต่ละโมดูลมีfoo_moduleโครงสร้างของตัวเอง(มีชื่อไม่ซ้ำกัน) คุณสามารถสร้างไฟล์พิเศษพร้อมอาร์เรย์struct { const char *module_name; const struct module *module_funcs; }และฟังก์ชันง่ายๆเพื่อค้นหาตารางนี้สำหรับโมดูลที่คุณต้องการ "โหลด" และส่งกลับตัวชี้ที่ถูกต้องจากนั้นใช้สิ่งนี้ ในสถานที่ของและdlopen dlsym
R .. GitHub STOP HELPING ICE

@R .. จริง แต่เพิ่มค่าบำรุงรักษาโดยต้องดูแลโครงสร้างโมดูล
user877329

3

ตัวอย่างที่ทันสมัยที่ตัวชี้ฟังก์ชันอาจมีขนาดแตกต่างจากตัวชี้ข้อมูล: ตัวชี้ฟังก์ชันสมาชิกคลาส C ++

อ้างจากhttps://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };

ขณะนี้มีthisตัวชี้ที่เป็นไปได้สองตัว

ตัวชี้ไปยังฟังก์ชันสมาชิกBase1สามารถใช้เป็นตัวชี้ไปยังฟังก์ชันสมาชิกDerivedได้เนื่องจากทั้งสองใช้this ตัวชี้เดียวกัน แต่ตัวชี้ไปยังฟังก์ชันสมาชิกของBase2ไม่สามารถใช้เป็น - เป็นตัวชี้ไปยังฟังก์ชันสมาชิกDerivedได้เนื่องจากthis ต้องปรับตัวชี้

มีหลายวิธีในการแก้ปัญหานี้ นี่คือวิธีที่คอมไพลเลอร์ Visual Studio ตัดสินใจจัดการ:

ตัวชี้ไปยังฟังก์ชันสมาชิกของคลาสที่สืบทอดแบบทวีคูณเป็นโครงสร้างจริงๆ

[Address of function]
[Adjustor]

size_tขนาดของตัวชี้ไปยังสมาชิกที่ฟังก์ชั่นของการเรียนที่ใช้มรดกหลายเป็นขนาดของตัวชี้ขนาดบวกของที่

tl; dr: เมื่อใช้การสืบทอดหลายตัวชี้ไปยังฟังก์ชันสมาชิก (ขึ้นอยู่กับคอมไพเลอร์เวอร์ชันสถาปัตยกรรม ฯลฯ ) จริง ๆ แล้วจะถูกเก็บเป็น

struct { 
    void * func;
    size_t offset;
}

ซึ่งเห็นได้ชัดว่ามีขนาดใหญ่กว่าไฟล์void *.


2

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

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

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


-1

ฉันรู้ว่านี้ไม่ได้รับความเห็นเกี่ยวกับตั้งแต่ปี 2012 แต่ผมคิดว่ามันจะเป็นประโยชน์กับเพิ่มที่ฉันทำรู้สถาปัตยกรรมที่มีมากชี้เข้ากันไม่ได้สำหรับข้อมูลและฟังก์ชั่นตั้งแต่สายที่ตรวจสอบสถาปัตยกรรมสิทธิพิเศษและดำเนินการข้อมูลเพิ่มเติม ปริมาณการหล่อจะช่วยไม่ได้ มันThe Mill


คำตอบนี้ผิด ตัวอย่างเช่นคุณสามารถแปลงตัวชี้ฟังก์ชันเป็นตัวชี้ข้อมูลและอ่านจากตัวชี้ (หากคุณมีสิทธิ์ในการอ่านจากที่อยู่นั้นตามปกติ) ผลลัพธ์นั้นสมเหตุสมผลพอ ๆ กับที่มันเป็นเช่นบน x86
Manuel Jacob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.