ความหมายของ int (*) (int *) = 5 (หรือค่าจำนวนเต็ม)


88

ฉันคิดไม่ออก:

int main() {
    int (*) (int *) = 5;
    return 0;
}

การมอบหมายข้างต้นรวบรวมด้วย g ++ c ++ 11 ฉันรู้ว่านั่นint (*) (int *)เป็นตัวชี้ไปยังฟังก์ชันที่ยอมรับ(int *)เป็นอาร์กิวเมนต์และส่งกลับค่า int แต่ฉันไม่เข้าใจว่าคุณจะเทียบเคียงเป็น 5 ได้อย่างไรในตอนแรกฉันคิดว่ามันเป็นฟังก์ชันที่คืนค่า 5 อย่างต่อเนื่อง (จากการเรียนรู้ล่าสุดของฉันใน F # น่าจะเป็นฮ่าฮ่า) จากนั้นฉันก็คิดสั้น ๆ ว่าตัวชี้ฟังก์ชันชี้ไปที่ตำแหน่งหน่วยความจำ 5 แต่ไม่ได้ผลชัดเจนและไม่มีค่าฐานสิบหก

คิดว่าอาจเป็นเพราะฟังก์ชันส่งคืน int และการกำหนด int ก็โอเค (อย่างใด) ฉันก็ลองสิ่งนี้ด้วย:

int * (*) (int *) = my_ptr

อยู่ที่ไหนmy_ptrประเภทประเภทint *เดียวกับตัวชี้ฟังก์ชันที่สองนี้เช่นเดียวกับในกรณีแรกที่มีประเภท int สิ่งนี้ไม่ได้รวบรวม การกำหนด 5 หรือค่า int ใด ๆ แทนที่จะmy_ptrไม่คอมไพล์สำหรับตัวชี้ฟังก์ชันนี้เช่นกัน

งานมอบหมายหมายความว่าอย่างไร?

อัปเดต 1

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

แก้ไข 1

ฉันใช้ gcc เวอร์ชัน 4.8.2 (ใน Ubuntu 4.8.2)

แก้ไข 2

อันที่จริงการเทียบเคียงกับทุกสิ่งที่ใช้ได้กับคอมไพเลอร์ของฉัน แม้กระทั่งการเทียบเคียงกับตัวแปร std :: string หรือชื่อฟังก์ชันที่ส่งกลับค่า double ก็ใช้ได้

แก้ไข 2.1

ที่น่าสนใจทำให้ตัวชี้ฟังก์ชันไปยังฟังก์ชันใด ๆ ที่ส่งคืนชนิดข้อมูลที่ไม่ใช่ตัวชี้จะปล่อยให้คอมไพล์เช่น

std::string (*) () = 5.6;

แต่ทันทีที่ตัวชี้ฟังก์ชันไปยังฟังก์ชันที่ส่งกลับตัวชี้บางตัวก็จะไม่คอมไพล์เช่นด้วย

some_data_type ** (*) () = any_value;

3
อืม ... มันดูไม่ถูกต้องและเสียงดังก็ไม่ยอมรับ อาจเป็นส่วนขยาย gcc (หรือจุดบกพร่อง)
Wintermute

4
g ++ คอมไพล์ แต่ gcc ไม่ทำงาน:error: expected identifier or '(' before ')' token
tivn

3
@ 0x499602D โปรดทราบว่ารหัสไม่ได้ตั้งชื่อให้กับตัวชี้ ด้วยคุณตั้งชื่อมันว่าint *x = 5 xด้วย int * (*x) (int *) = 5มันจะไม่คอมไพล์ (แม้ว่าจะคอมไพล์เป็นรหัส C)
เลขที่

5
กรณีทดสอบที่ลดลง: int(*) = 5;และint(*);
Johannes Schaub - litb

13
ดูเหมือนว่าgcc.gnu.org/bugzilla/show_bug.cgi?id=60680
TC

คำตอบ:


60

เป็นบั๊กใน g ++

 int (*) (int *) 

เป็นชื่อประเภท

ใน C ++ คุณไม่สามารถประกาศด้วยชื่อประเภทที่ไม่มีตัวระบุ

ดังนั้นสิ่งนี้จะรวบรวมด้วย g ++

 int (*) (int *) = 5;

และสิ่งนี้รวบรวมเช่นกัน:

 int (*) (int *);

แต่เป็นการประกาศที่ไม่ถูกต้องทั้งคู่

แก้ไข :

TCกล่าวถึงในความคิดเห็น Bugzilla ข้อผิดพลาด60680กับกรณีทดสอบที่คล้ายกันแต่ก็ยังไม่ได้รับการอนุมัติ ข้อผิดพลาดได้รับการยืนยันใน bugzilla

แก้ไข 2 :

เมื่อการประกาศสองรายการข้างต้นอยู่ในขอบเขตไฟล์ g ++ จะออกการวินิจฉัยอย่างถูกต้อง (ไม่สามารถออกการวินิจฉัยที่ขอบเขตบล็อกได้)

แก้ไข 3 :

ฉันตรวจสอบแล้วและสามารถสร้างปัญหาซ้ำได้ในรุ่นล่าสุดของ g ++ เวอร์ชัน 4 (4.9.2), เวอร์ชันก่อนวางจำหน่ายล่าสุดล่าสุด (5.0.1 20150412) และเวอร์ชันทดลองล่าสุด 6 (6.0.0 20150412)


5
MSVC ปฏิเสธรหัสที่โพสต์ที่แก้ไขด้วยerror C2059: syntax error : ')'
Weather Vane

ถ้าเป็นชื่อประเภททำไมไม่ 'int (*) (int *) int_func;' งาน?
Konrad

1
สำหรับบั๊กซิลล่า GCC "ใหม่" คือบั๊กที่ได้รับการยืนยัน (ข้อบกพร่องที่ไม่ได้รับการยืนยันคือ "UNCONFIRMED")
TC

4
@KonradKapp: มันทำงานได้ดีถ้าคุณพูดซึ่งประกาศตัวชี้ฟังก์ชั่นที่มีชื่อว่าint (*int_func)(int *); int_func
Edward

3
@KonradKapp C ++ ใช้สัญลักษณ์ infix สำหรับวางตัวระบุ เหตุผลเดียวกันก็คือint x[5];ไม่ใช่int[5] x;
MM

28

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

ในทางตรงกันข้ามclang++บ่น:

funnycast.cpp:3:11: error: expected expression
    int (*) (int *) = 5;
          ^
funnycast.cpp:3:18: error: expected '(' for function-style cast or type construction
    int (*) (int *) = 5;
             ~~~ ^
funnycast.cpp:3:19: error: expected expression
    int (*) (int *) = 5;
                  ^
3 errors generated.

นี่เป็นลักษณะการทำงานที่คาดไว้เนื่องจากบรรทัดที่ละเมิดไม่ใช่ C ++ ที่ถูกต้อง โดยอ้างว่าเป็นการมอบหมายงาน (เนื่องจาก=) แต่ไม่มีตัวระบุ


9

ดังที่คำตอบอื่น ๆ ได้ชี้ให้เห็นว่ามันเป็นข้อบกพร่องที่

int (*) (int *) = 5;

รวบรวม การประมาณที่สมเหตุสมผลของข้อความนี้ที่คาดว่าจะมีความหมายคือ:

int (*proc)(int*) = (int (*)(int*))(5);

ตอนนี้procเป็นตัวชี้ต่อฟังก์ชันที่คาดว่าแอดเดรส5จะเป็นแอดเดรสฐานของฟังก์ชันที่รับint*และส่งกลับintไฟล์.

ในไมโครคอนโทรลเลอร์ / ไมโครโปรเซสเซอร์บางตัว5อาจเป็นรหัสแอดเดรสที่ถูกต้องและอาจเป็นไปได้ที่จะค้นหาฟังก์ชันดังกล่าวที่นั่น

ในคอมพิวเตอร์ที่ใช้งานทั่วไปส่วนใหญ่หน้าแรกของหน่วยความจำ (ที่อยู่0-1023สำหรับเพจ 4K) จะไม่ถูกต้องโดยเจตนา (ไม่ได้แมป) เพื่อที่จะตรวจจับการnullเข้าถึงตัวชี้

ดังนั้นในขณะที่พฤติกรรมขึ้นอยู่กับแพลตฟอร์มเราสามารถคาดหวังได้อย่างสมเหตุสมผลว่าข้อบกพร่องของเพจจะเกิดขึ้นเมื่อ*procมีการเรียกใช้ (เช่น(*proc)(&v)) ก่อนเวลาที่*procถูกเรียกไม่มีอะไรผิดปกติเกิดขึ้น

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


2
/usr/lib/gcc/x86_64-pc-cygwin/4.9.2/cc1plus.exe -da so.cpp

บรรทัดคำสั่งนี้สร้างไฟล์ระดับกลางจำนวนมาก คนแรกso.cpp.170r.expandกล่าวว่า:

...
int main() ()
{
  int D.2229;
  int _1;

;;   basic block 2, loop depth 0
;;    pred:       ENTRY
  _1 = 0;
;;    succ:       3

;;   basic block 3, loop depth 0
;;    pred:       2
<L0>:
  return _1;
;;    succ:       EXIT

}
...

สิ่งนี้ยังไม่ตอบโจทย์ว่าเกิดอะไรขึ้นอย่างแน่นอน แต่ควรเป็นขั้นตอนในทิศทางที่ถูกต้อง


น่าสนใจ. ไฟล์ระดับกลางเหล่านี้มีจุดประสงค์อะไร?
Konrad

@KonradKapp ในการสร้างรหัสเครื่องจากโค้ดของมนุษย์นั้นค่อนข้างเป็นกระบวนการที่ซับซ้อน (โดยเฉพาะอย่างยิ่งถ้าคุณต้องการให้คอมไพเลอร์ของคุณปรับเอาต์พุตให้เหมาะสม) เนื่องจากการคอมไพล์มีความซับซ้อนมากจึงไม่ได้ทำในขั้นตอนเดียวคอมไพเลอร์ส่วนใหญ่จึงมีรูปแบบการเป็นตัวแทนระดับกลาง (IR)
11684

2
อีกเหตุผลหนึ่งของการมี IR คือถ้าคุณมี IR ที่กำหนดไว้อย่างดีคุณสามารถแยกส่วนหน้าและส่วนหลังของคอมไพเลอร์ของคุณได้ (เช่นฟรอนต์เอนด์คอมไพล์ C ไปยัง IR ของคุณแบ็คเอนด์คอมไพล์ IR เป็นรหัสเครื่องของ Intel ตอนนี้ถ้าคุณต้องการเพิ่มการสนับสนุน ARM คุณต้องใช้แบ็คเอนด์ที่สองเท่านั้นและหากคุณต้องการคอมไพล์ Go คุณต้องมี ฟรอนต์เอนด์ที่สองและยิ่งไปกว่านั้นคอมไพเลอร์ Go รองรับทั้ง Intel และ ARM ในทันทีเนื่องจากคุณสามารถนำแบ็คเอนด์ทั้งสองกลับมาใช้ใหม่ได้
11684

@ 11684 โอเคเข้าท่า น่าสนใจมาก. ฉันไม่สามารถระบุได้ว่า Roland ให้คำตอบนี้เป็นภาษาอะไร ... ดูเหมือนว่ามีการประกอบบางอย่างผสมกับ C.
Konrad

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