การเปรียบเทียบตัวชี้ทำงานใน C อย่างไร การเปรียบเทียบพอยน์เตอร์ที่ไม่ได้ชี้ไปที่อาเรย์นั้นเป็นสิ่งที่ดีหรือไม่?


33

ใน K&R (ภาษาการเขียนโปรแกรม C รุ่นที่ 2) บทที่ 5 ฉันอ่านต่อไปนี้:

ขั้นแรกให้เปรียบเทียบตัวชี้ภายใต้สถานการณ์บางอย่าง ถ้าpและqชี้ไปที่สมาชิกของอาร์เรย์เดียวกันแล้วความสัมพันธ์ที่ชอบ==, !=, <, >=ฯลฯ การทำงานอย่างถูกต้อง

ซึ่งดูเหมือนว่าบ่งบอกว่ามีเพียงพอยน์เตอร์ที่ชี้ไปยังอาร์เรย์เดียวกันเท่านั้นที่สามารถเปรียบเทียบได้

อย่างไรก็ตามเมื่อฉันลองใช้รหัสนี้

    char t = 't';
    char *pt = &t;
    char x = 'x';
    char *px = &x;

    printf("%d\n", pt > px);

1 ถูกพิมพ์ไปที่หน้าจอ

ก่อนอื่นฉันคิดว่าฉันจะไม่ได้กำหนดหรือบางประเภทหรือข้อผิดพลาดเพราะptและpxไม่ได้ชี้ไปที่อาร์เรย์เดียวกัน (อย่างน้อยก็ในความเข้าใจของฉัน)

เป็นpt > pxเพราะพอยน์เตอร์ทั้งสองชี้ไปที่ตัวแปรที่เก็บอยู่ในสแต็กและสแต็กโตขึ้นดังนั้นที่อยู่หน่วยความจำของtมากกว่าที่อยู่x? อะไรคือสาเหตุที่pt > pxแท้จริง

ฉันสับสนมากขึ้นเมื่อนำ malloc เข้ามานอกจากนี้ใน K&R ในบทที่ 8.7 เขียนต่อไปนี้:

อย่างไรก็ตามยังมีข้อสันนิษฐานหนึ่งข้อที่ชี้ไปยังบล็อกที่แตกต่างกันที่ส่งคืนโดยsbrkสามารถเปรียบเทียบได้อย่างมีความหมาย สิ่งนี้ไม่รับประกันโดยมาตรฐานที่อนุญาตให้ทำการเปรียบเทียบตัวชี้ภายในอาเรย์เท่านั้น ดังนั้นรุ่นนี้mallocเป็นแบบพกพาเฉพาะในเครื่องที่การเปรียบเทียบตัวชี้ทั่วไปมีความหมาย

ฉันไม่มีปัญหาในการเปรียบเทียบตัวชี้ที่ชี้ไปที่ space malloced บน heap กับตัวชี้ที่ชี้ไปที่ตัวแปรสแต็ก

ตัวอย่างเช่นรหัสต่อไปนี้ทำงานได้ดีโดยมี1การพิมพ์:

    char t = 't';
    char *pt = &t;
    char *px = malloc(10);
    strcpy(px, pt);
    printf("%d\n", pt > px);

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

กระนั้นฉันก็ยังสับสนกับสิ่งที่ฉันกำลังอ่านใน K&R

เหตุผลที่ฉันถามก็เพราะศาสตราจารย์ จริงๆแล้วมันเป็นคำถามสอบ เขาให้รหัสต่อไปนี้:

struct A {
    char *p0;
    char *p1;
};

int main(int argc, char **argv) {
    char a = 0;
    char *b = "W";
    char c[] = [ 'L', 'O', 'L', 0 ];

   struct A p[3];
    p[0].p0 = &a;
    p[1].p0 = b;
    p[2].p0 = c;

   for(int i = 0; i < 3; i++) {
        p[i].p1 = malloc(10);
        strcpy(p[i].p1, p[i].p0);
    }
}

สิ่งเหล่านี้ประเมินถึง:

  1. p[0].p0 < p[0].p1
  2. p[1].p0 < p[1].p1
  3. p[2].p0 < p[2].p1

คำตอบคือ0, และ10

(อาจารย์ของฉันมีข้อจำกัดความรับผิดชอบในการสอบว่าคำถามนั้นมีไว้สำหรับ Ubuntu Linux 16.04, สภาพแวดล้อมการเขียนโปรแกรมเวอร์ชัน 64 บิต)

(หมายเหตุบรรณาธิการ: ถ้าอนุญาตให้แท็กเพิ่มเติมส่วนสุดท้ายจะรับประกัน , , และอาจจะถ้าจุดของคำถาม / ชั้นเป็นรายละเอียดการใช้งานระบบปฏิบัติการระดับต่ำโดยเฉพาะอย่างยิ่งพกพา C. )


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

13
นอกจากนี้คุณไม่ควรเรียนรู้ C จาก K&R สำหรับการเริ่มต้นภาษาที่ได้รับผ่านจำนวนมากของการเปลี่ยนแปลงตั้งแต่นั้นมา และตามจริงแล้วโค้ดตัวอย่างในนั้นมาจากช่วงเวลาที่ความตึงเครียดมากกว่าการอ่านง่ายมีค่า
paxdiablo

5
ไม่ไม่รับประกันว่าจะใช้งานได้ มันอาจล้มเหลวในการปฏิบัติกับเครื่องที่มีโมเดลหน่วยความจำที่แบ่งกลุ่ม ดูที่C มีค่า std :: น้อยกว่าจาก C ++ หรือไม่ สำหรับเครื่องจักรที่ทันสมัยที่สุดมันจะเกิดขึ้นในการทำงานแม้จะมี UB
Peter Cordes

6
@ อดัม: ปิด แต่นี่เป็น UB จริง ๆ (ยกเว้นว่าคอมไพเลอร์ที่ OP ใช้อยู่นั้น GCC เลือกที่จะกำหนดมันอาจ) แต่ UB ไม่ได้แปลว่า "ระเบิดอย่างแน่นอน"; หนึ่งในพฤติกรรมที่เป็นไปได้สำหรับ UB คือการทำงานอย่างที่คุณคาดไว้ !! นี่คือสิ่งที่ทำให้ UB น่ารังเกียจ มันสามารถทำงานได้ใน debug build และ fail โดยการเปิดใช้งาน optimization หรือในทางกลับกันหรือ break ขึ้นอยู่กับโค้ดที่อยู่รอบ ๆ การเปรียบเทียบตัวชี้อื่น ๆ จะยังคงให้คำตอบแก่คุณ แต่ภาษานั้นไม่ได้กำหนดว่าคำตอบนั้นจะหมายถึงอะไร (ถ้ามี) ไม่อนุญาตให้หยุดทำงาน มันเป็น UB อย่างแท้จริง
ปีเตอร์

3
@ อดัม: โอ้ใช่ไม่เป็นไรส่วนแรกของความคิดเห็นของฉันฉันอ่านผิดของคุณ แต่คุณเรียกร้องการเปรียบเทียบตัวชี้อื่น ๆ จะยังคงให้คำตอบ ที่ไม่เป็นความจริง. นั่นจะเป็นผลลัพธ์ที่ไม่ได้ระบุไม่ใช่ UB แบบเต็ม UB นั้นแย่กว่ามากและหมายความว่าโปรแกรมของคุณสามารถ segfault หรือ SIGILL หากการดำเนินการมาถึงคำสั่งนั้นด้วยอินพุตเหล่านั้น (ณ จุดใด ๆ ก่อนหรือหลังที่เกิดขึ้นจริง) (น่าเชื่อถือได้เฉพาะใน x86-64 หาก UB สามารถมองเห็นได้ในเวลารวบรวม แต่โดยทั่วไปสิ่งที่สามารถเกิดขึ้นได้) ส่วนหนึ่งของจุดของ UB คือการปล่อยให้คอมไพเลอร์ทำให้สมมติฐาน "ไม่ปลอดภัย" ในขณะที่สร้าง asm
ปีเตอร์

คำตอบ:


33

ตามที่มาตรฐาน C11 , ผู้ประกอบการเชิงสัมพันธ์<, <=, >และ>=อาจจะใช้เฉพาะในตัวชี้ไปยังองค์ประกอบของอาร์เรย์เดียวกันหรือวัตถุ struct สิ่งนี้สะกดออกมาในหัวข้อ 6.5.8p5:

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

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

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

ตัวอย่างเช่นตัวประมวลผล x86 ที่รันในโหมด 8086 จริงมีโมเดลหน่วยความจำแบบแบ่งเซ็กเมนต์โดยใช้เซ็กเมนต์ 16 บิตและออฟเซ็ต 16 บิตเพื่อสร้างที่อยู่ 20 บิต ดังนั้นในกรณีนี้ที่อยู่ไม่ได้แปลงเป็นจำนวนเต็มอย่างแน่นอน

ตัวดำเนินการความเสมอภาค==และ!=อย่างไรก็ตามไม่มีข้อ จำกัด นี้ สามารถใช้ระหว่างตัวชี้สองตัวใด ๆ กับชนิดที่เข้ากันได้หรือตัวชี้ NULL ดังนั้นการใช้==หรือ!=ทั้งสองตัวอย่างของคุณจะสร้างรหัส C ที่ถูกต้อง

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

เกี่ยวกับคำถามสอบที่อาจารย์ของคุณกำหนดขึ้นมานั้นมีข้อสันนิษฐานหลายข้อ:

  • โมเดลหน่วยความจำแบบแบนมีอยู่โดยที่มีความสอดคล้องแบบ 1 ต่อ 1 ระหว่างที่อยู่และค่าจำนวนเต็ม
  • ค่าของตัวชี้ที่แปลงแล้วนั้นจะพอดีกับประเภทจำนวนเต็ม
  • การใช้งานนั้นปฏิบัติต่อพอยน์เตอร์เป็นจำนวนเต็มเมื่อทำการเปรียบเทียบโดยไม่ใช้ประโยชน์จากพฤติกรรมที่ไม่ได้กำหนด
  • มีการใช้สแต็กและเก็บตัวแปรภายในไว้ที่นั่น
  • ใช้ฮีปเพื่อดึงหน่วยความจำที่จัดสรรออกมา
  • สแต็ก (และตัวแปรท้องถิ่น) ปรากฏขึ้นที่ที่อยู่สูงกว่าฮีป (และดังนั้นจึงจัดสรรวัตถุ)
  • ค่าคงที่สตริงนั้นจะปรากฏที่แอดเดรสต่ำกว่าจากนั้นก็จะเป็นฮีป

หากคุณต้องรันโค้ดนี้บนสถาปัตยกรรมและ / หรือคอมไพเลอร์ที่ไม่เป็นไปตามสมมติฐานเหล่านี้คุณก็จะได้ผลลัพธ์ที่แตกต่างกันมาก

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


3
@Shisui ถึงแม้จะเป็นเช่นนั้นคุณก็ยังไม่ควรพึ่งพาผลลัพธ์ คอมไพเลอร์สามารถก้าวร้าวมากเมื่อมันมาถึงการเพิ่มประสิทธิภาพและจะใช้พฤติกรรมที่ไม่ได้กำหนดเป็นโอกาสในการทำเช่นนั้น เป็นไปได้ว่าการใช้คอมไพเลอร์และ / หรือการตั้งค่าการเพิ่มประสิทธิภาพที่แตกต่างกันสามารถสร้างเอาต์พุตที่แตกต่างกัน
dbush

2
@Shisui: โดยทั่วไปแล้วจะเกิดขึ้นกับเครื่องที่มีหน่วยความจำแบบแบนเช่น x86-64 คอมไพเลอร์บางตัวสำหรับระบบดังกล่าวอาจกำหนดพฤติกรรมในเอกสารของพวกเขา แต่ถ้าไม่เช่นนั้นพฤติกรรม "บ้า" อาจเกิดขึ้นได้เนื่องจาก UB ที่คอมไพล์เวลาเห็น (ในทางปฏิบัติฉันไม่คิดว่าจะมีใครต้องการดังนั้นจึงไม่ใช่สิ่งที่ผู้รวบรวมหลักมองหาและ "พยายามทำลาย")
Peter Cordes

1
เช่นถ้าคอมไพเลอร์เห็นว่าหนึ่งเส้นทางของการดำเนินการจะนำไปสู่<ระหว่างmallocผลและตัวแปรท้องถิ่น (การจัดเก็บอัตโนมัติเช่นสแต็ค) ก็สามารถสันนิษฐานได้ว่าเส้นทางของการดำเนินการไม่เคยถ่ายและเพียงรวบรวมฟังก์ชั่นทั้งหมดเพื่อการud2เรียนการสอน - ข้อยกเว้นในการใช้งานซึ่งเคอร์เนลจะจัดการโดยส่ง SIGILL ไปยังกระบวนการ) GCC / เสียงดังกังวานทำสิ่งนี้ในทางปฏิบัติสำหรับ UB ชนิดอื่นเช่นตกจากจุดสิ้นสุดของvoidฟังก์ชันที่ ไม่ใช่ godbolt.orgตอนนี้ดูเหมือนว่าจะลง แต่ลองคัดลอก / วางint foo(){int x=2;}และบันทึกการขาดret
Peter Cordes

4
@Shisui: TL: DR: มันไม่ใช่พกพา C แม้ว่ามันจะทำงานได้ดีบน x86-64 Linux การตั้งสมมติฐานเกี่ยวกับผลลัพธ์ของการเปรียบเทียบนั้นบ้าไปแล้ว หากคุณไม่ได้อยู่ในหัวข้อหลักกองกระทู้ของคุณจะได้รับการจัดสรรแบบไดนามิกโดยใช้กลไกเดียวกันmallocการใช้งานที่จะได้รับหน่วยความจำเพิ่มเติมจากระบบปฏิบัติการจึงมีเหตุผลที่จะคิดว่า vars ท้องถิ่นของคุณ (สแต็คด้าย) อยู่เหนือไม่มีmallocที่จัดสรรแบบไดนามิก การเก็บรักษา
ปีเตอร์

2
@PeterCordes: สิ่งที่จำเป็นคือการจดจำลักษณะต่าง ๆ ของพฤติกรรมในฐานะ "การกำหนดทางเลือก" ซึ่งการใช้งานอาจกำหนดได้หรือไม่ตามความต้องการ แต่ต้องระบุด้วยวิธีทดสอบ (เช่นมาโครที่กำหนดไว้ล่วงหน้า) หากไม่ทำเช่นนั้น นอกจากนี้แทนที่จะบอกว่าสถานการณ์ใด ๆ ที่ผลกระทบของการปรับให้เหมาะสมจะสังเกตได้ว่าเป็น "พฤติกรรมที่ไม่ได้กำหนด" มันจะมีประโยชน์มากกว่าที่จะกล่าวว่าเครื่องมือเพิ่มประสิทธิภาพอาจพิจารณาพฤติกรรมบางอย่างว่า "ไม่สามารถสังเกตได้" หากพวกเขาบ่งชี้ว่า ทำเช่นนั้น ตัวอย่างเช่นint x,y;การใช้งาน ...
supercat

12

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

ก่อนอื่นฉันคิดว่าฉันจะไม่ได้กำหนดหรือบางประเภทหรือข้อผิดพลาดเนื่องจาก pt px ไม่ได้ชี้ไปที่อาร์เรย์เดียวกัน (อย่างน้อยก็ในความเข้าใจของฉัน)

ไม่ผลลัพธ์ขึ้นอยู่กับการนำไปใช้งานและปัจจัยอื่น ๆ ที่คาดเดาไม่ได้

นอกจากนี้ยังเป็น pt> px เนื่องจากพอยน์เตอร์ทั้งสองชี้ไปที่ตัวแปรที่เก็บอยู่ในสแต็กและสแต็กโตขึ้นดังนั้นที่อยู่หน่วยความจำของ t จึงมากกว่าของ x? ทำไม pt> px ถึงเป็นจริง

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

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

ลองดูที่ข้อกำหนด C , §6.5.8ในหน้า 85 ซึ่งพูดถึงผู้ประกอบการสัมพันธ์ (เช่นผู้ประกอบการเปรียบเทียบที่คุณกำลังใช้) โปรดทราบว่าสิ่งนี้ใช้ไม่ได้กับโดยตรง!=หรือ==การเปรียบเทียบ

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

ในกรณีอื่น ๆ พฤติกรรมจะไม่ได้กำหนด

ประโยคสุดท้ายนั้นสำคัญ ในขณะที่ฉันลดบางกรณีที่ไม่เกี่ยวข้องกันเพื่อประหยัดพื้นที่มีกรณีหนึ่งที่สำคัญสำหรับเราคือ: สองอาร์เรย์ไม่ใช่ส่วนหนึ่งของ struct / วัตถุรวมเดียวกัน1และเรากำลังเปรียบเทียบพอยน์เตอร์กับทั้งสองอาร์เรย์ นี่คือพฤติกรรมที่ไม่ได้กำหนด

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

1 พอยน์เตอร์เป็นสองอาร์เรย์ที่แตกต่างกันซึ่งเป็นส่วนหนึ่งของโครงสร้างเดียวกันสามารถเปรียบเทียบได้เนื่องจากสิ่งนี้อยู่ภายใต้ส่วนคำสั่งที่ทั้งสองอาร์เรย์เป็นส่วนหนึ่งของวัตถุรวมเดียวกัน (โครงสร้าง)


1
ที่สำคัญกว่านั้นคือด้วยtและxถูกนิยามในฟังก์ชั่นเดียวกันมันไม่มีเหตุผลที่จะคิดอะไรเกี่ยวกับคอมไพเลอร์ที่กำหนดเป้าหมาย x86-64 จะวางโครงคนในเฟรมสแต็กสำหรับฟังก์ชั่นนี้ สแต็คที่เพิ่มขึ้นลงไม่มีอะไรเกี่ยวข้องกับลำดับการประกาศของตัวแปรในฟังก์ชั่นเดียว แม้ในฟังก์ชั่นที่แยกกันถ้าใครสามารถแทรกเข้าไปในอื่น ๆ แล้วคนในท้องถิ่นของฟังก์ชั่น "เด็ก" ยังคงสามารถผสมกับผู้ปกครอง
ปีเตอร์

1
คอมไพเลอร์ของคุณอาจเพิ่มประสิทธิภาพจากฟังก์ชั่นทั้งหมดรวมทั้งผลข้างเคียงที่สามารถมองเห็นได้ ไม่คุยโว: สำหรับชนิดอื่น ๆ ของ UB (เช่นล้มปิดท้ายของที่ไม่ใช่voidฟังก์ชั่น) กรัม ++ ++ และเสียงดังกราวทำจริงๆว่าในทางปฏิบัติ: godbolt.org/z/g5vesBพวกเขา สมมติว่าเส้นทางของการดำเนินการไม่ได้เกิดขึ้นเพราะมันนำไปสู่ ​​UB และรวบรวมบล็อกพื้นฐานดังกล่าวไปยังคำสั่งที่ผิดกฎหมาย หรือไม่มีคำแนะนำเลยเพียงแค่ผ่านไปอย่างเงียบ ๆ ไม่ว่าอะไรจะเกิดขึ้นถ้ามีการเรียกใช้ฟังก์ชันนั้น (ด้วยเหตุผลบางอย่างgccไม่ได้ทำเช่นนี้เท่านั้นg++)
ปีเตอร์

6

แล้วถามว่าอะไร

p[0].p0 < p[0].p1
p[1].p0 < p[1].p1
p[2].p0 < p[2].p1

ประเมินเพื่อ คำตอบคือ 0, 1 และ 0

คำถามเหล่านี้ลดเป็น:

  1. ฮีปนั้นสูงหรือต่ำกว่าสแต็ก
  2. ฮีปอยู่เหนือหรือใต้ส่วนสตริงตัวอักษรของโปรแกรมหรือไม่
  3. เช่นเดียวกับ [1]

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

<empty>
text
rodata
rwdata
bss
< empty, used for heap >
...
stack
kernel

แต่ปัจจุบันหลายยูนิเซฟ (และระบบทางเลือก) ไม่สอดคล้องกับประเพณีเหล่านั้น นอกจากว่าพวกเขาจะนำคำถามมาล่วงหน้าด้วย "ณ ปี 1992" ตรวจสอบให้แน่ใจว่าได้ให้ -1 บน eval


3
ไม่ได้กำหนดการใช้งานไม่ได้กำหนด! คิดถึงวิธีนี้ในอดีตอาจแตกต่างกันระหว่างการนำไปใช้ แต่การนำไปปฏิบัติควรจัดทำเป็นเอกสารว่ามีการตัดสินใจพฤติกรรมอย่างไร ลักษณะการทำงานวิธีการหลังอาจแตกต่างกันในลักษณะใดและการดำเนินการไม่ได้ที่จะบอกคุณหมอบ :-)
paxdiablo

1
@paxdiablo: ตามเหตุผลโดยผู้เขียนมาตรฐาน "พฤติกรรมที่ไม่ได้กำหนด ... ยังระบุพื้นที่ของการขยายภาษาที่เป็นไปได้: ผู้ดำเนินการอาจเพิ่มภาษาโดยการให้คำจำกัดความของพฤติกรรมที่ไม่ได้กำหนดอย่างเป็นทางการ" เหตุผลเพิ่มเติมกล่าวว่า "เป้าหมายคือเพื่อให้โปรแกรมเมอร์มีโอกาสในการต่อสู้เพื่อสร้างโปรแกรม C ที่ทรงพลังซึ่งยังพกพาได้สูงโดยไม่ต้องดูว่าโปรแกรม C มีประโยชน์อย่างสมบูรณ์แบบที่ไม่ควรพกพาดังนั้นคำวิเศษณ์อย่างเคร่งครัด" ผู้เขียนคอมไพเลอร์เชิงพาณิชย์เข้าใจเรื่องนี้ แต่ผู้เขียนคอมไพเลอร์คนอื่นไม่เข้าใจ
supercat

มีอีกด้านหนึ่งที่กำหนดไว้ การเปรียบเทียบตัวชี้ถูกลงชื่อดังนั้นขึ้นอยู่กับเครื่อง / os / คอมไพเลอร์บางที่อยู่อาจถูกตีความว่าเป็นลบ ตัวอย่างเช่นเครื่อง 32 บิตที่วางสแต็กที่ 0xc << 28 น่าจะแสดงตัวแปรอัตโนมัติที่ที่อยู่ผู้ให้เช่าน้อยกว่าฮีปหรือโรดาทา
mevets

1
@mevets: มาตรฐานระบุสถานการณ์ใด ๆ ที่การลงนามของพอยน์เตอร์ในการเปรียบเทียบจะเป็นที่สังเกตได้หรือไม่? ฉันคาดหวังว่าหากแพลตฟอร์ม 16 บิตอนุญาตให้วัตถุมีขนาดใหญ่กว่า 32768 ไบต์และarr[]เป็นวัตถุเช่นนั้นมาตรฐานจะมอบอำนาจให้arr+32768เปรียบเทียบมากกว่าarrแม้ว่าการเปรียบเทียบตัวชี้ที่ลงชื่อจะต้องรายงานเป็นอย่างอื่น
supercat

ฉันไม่รู้ มาตรฐาน C กำลังโคจรอยู่ในวงกลมที่เก้าของดันเต้สวดภาวนาเพื่อนาเซียเซีย K&R อ้างอิงโดยเฉพาะและคำถามสอบ #UB เป็นเศษเล็กเศษน้อยจากกลุ่มทำงานที่ขี้เกียจ
mevets

1

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

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

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

เป็นตัวอย่างของสถานการณ์ที่แม้แต่การเปรียบเทียบความเท่าเทียมกันทำงานแบบไร้สาระใน gcc และเสียงดังกราวให้พิจารณา:

extern int x[],y[];
int test(int i)
{
    int *p = y+i;
    y[0] = 4;
    if (p == x+10)
        *p = 1;
    return y[0];
}

ทั้ง clang และ gcc จะสร้างโค้ดที่จะส่งคืน 4 เสมอแม้ว่าxจะเป็นสิบองค์ประกอบyตามมาทันทีและiเป็นศูนย์ทำให้การเปรียบเทียบเป็นจริงและp[0]เขียนด้วยค่า 1 ฉันคิดว่าสิ่งที่เกิดขึ้นคือการเขียนการเพิ่มประสิทธิภาพเพียงครั้งเดียว ฟังก์ชั่นราวกับว่าถูกแทนที่ด้วย*p = 1; x[10] = 1;โค้ดหลังจะเทียบเท่าหากคอมไพเลอร์ตีความ*(x+10)ว่าเทียบเท่า*(y+i)แต่น่าเสียดายที่ขั้นตอนการปรับให้เหมาะสมแบบดาวน์สตรีมตระหนักว่าการเข้าถึงx[10]จะถูกกำหนดเฉพาะเมื่อxมีองค์ประกอบอย่างน้อย 11 องค์ประกอบซึ่งจะทำให้ไม่สามารถเข้าถึงผลกระทบyนั้นได้

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


0

ง่ายมาก: การเปรียบเทียบพอยน์เตอร์ไม่สมเหตุสมผลเนื่องจากตำแหน่งหน่วยความจำสำหรับวัตถุไม่เคยรับประกันว่าจะอยู่ในลำดับเดียวกันกับที่คุณประกาศ ข้อยกเว้นคืออาร์เรย์ & อาร์เรย์ [0] ต่ำกว่า & อาร์เรย์ [1] นั่นคือสิ่งที่ K&R ชี้ให้เห็น ในทางปฏิบัติสมาชิกที่อยู่ของ struct ยังอยู่ในลำดับที่คุณประกาศพวกเขาในประสบการณ์ของฉัน ไม่รับประกันกับที่ .... ยกเว้นอีกอย่างคือถ้าคุณเปรียบเทียบตัวชี้ให้เท่ากัน เมื่อตัวชี้หนึ่งเท่ากับอีกคุณรู้ว่ามันชี้ไปที่วัตถุเดียวกัน มันคืออะไร คำถามสอบไม่ดีถ้าคุณถามฉัน ขึ้นอยู่กับ Ubuntu Linux 16.04, สภาพแวดล้อมการเขียนโปรแกรมเวอร์ชัน 64 บิตสำหรับคำถามสอบหรือไม่? จริงเหรอ


เทคนิคอาร์เรย์ไม่ได้จริงๆข้อยกเว้นเนื่องจากคุณไม่ได้ประกาศarr[0], arr[1]ฯลฯ แยกต่างหาก คุณประกาศarrโดยรวมเพื่อให้การจัดเรียงองค์ประกอบอาร์เรย์แต่ละรายการเป็นปัญหาที่แตกต่างจากที่อธิบายไว้ในคำถามนี้
paxdiablo

1
องค์ประกอบโครงสร้างได้รับการรับรองว่าเป็นไปตามลำดับซึ่งรับประกันได้ว่าอาจใช้memcpyเพื่อคัดลอกส่วนที่ต่อเนื่องกันของโครงสร้างและส่งผลกระทบต่อองค์ประกอบทั้งหมดในนั้นและไม่ส่งผลกระทบต่อสิ่งอื่น มาตรฐานเป็นเรื่องเกี่ยวกับคำศัพท์ที่เลอะเทอะเกี่ยวกับสิ่งที่ชนิดของเลขคณิตชี้สามารถทำได้กับโครงสร้างหรือmalloc()การจัดเก็บข้อมูลที่จัดสรร offsetofแมโครจะค่อนข้างไร้ประโยชน์หากไม่สามารถไปชนิดเดียวกันของเลขคณิตชี้กับไบต์ของโครงสร้างเช่นเดียวกับที่char[]แต่มาตรฐานไม่ชัดบอกว่าไบต์ของโครงสร้างที่มี (หรือสามารถใช้เป็น) วัตถุอาร์เรย์
supercat

-4

คำถามยั่วยุ!

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

ไม่ควรแปลกใจ

inarguably, ความเข้าใจผิดรอบแนวคิดและการใช้งานของตัวชี้แสดงให้เห็นเด่นสาเหตุร้ายแรงความล้มเหลวในการเขียนโปรแกรมโดยทั่วไป

การรับรู้ถึงความเป็นจริงนี้เห็นได้ชัดในความแพร่หลายของภาษาที่ออกแบบมาเพื่อการจัดการโดยเฉพาะและโดยเฉพาะอย่างยิ่งเพื่อหลีกเลี่ยงความท้าทายที่พอยน์เตอร์แนะนำเข้าด้วยกัน คิดว่า C ++ และอนุพันธ์อื่น ๆ ของ C, Java และความสัมพันธ์ของมัน, Python และสคริปต์อื่น ๆ - เป็นเพียงสิ่งที่โดดเด่นและแพร่หลายมากขึ้นและสั่งมากหรือน้อยในการจัดการกับปัญหา

การพัฒนาความรู้ความเข้าใจในหลักการพื้นฐานดังนั้นจึงต้องเกี่ยวข้องเพื่อบุคคลทุกคนที่ปรารถนาที่จะเป็นเลิศในการเขียนโปรแกรม - โดยเฉพาะอย่างยิ่งในระดับระบบ

ฉันคิดว่านี่เป็นสิ่งที่ครูของคุณแสดงให้เห็น

และธรรมชาติของ C ทำให้ยานพาหนะสะดวกสำหรับการสำรวจครั้งนี้ ชัดเจนน้อยกว่าการชุมนุม - แม้ว่าอาจเข้าใจได้ง่ายกว่าและยังชัดเจนกว่าภาษาโดยอิงจากสิ่งที่เป็นนามธรรมที่ลึกซึ้งของสภาพแวดล้อมการดำเนินการ

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

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

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

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

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

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

แน่นอนว่าส่วนหนึ่งของความสับสนเกิดขึ้นจากผลของการเรียกซ้ำที่เกิดขึ้นภายในหลักการของตัวชี้ - และความท้าทายที่เกิดขึ้นในเนื้อหาที่แตกต่างจากที่อยู่

คุณคาดเดาได้ค่อนข้างถูกต้อง

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

และผู้มีส่วนร่วมหลายคนยืนยัน: พอยน์เตอร์เป็นเพียงตัวเลข บางครั้งสิ่งที่ซับซ้อนกว่าแต่ก็ยังไม่เกินจำนวน

ความรุนแรงที่น่าขบขันที่ได้รับจากการโต้แย้งครั้งนี้เผยให้เห็นเกี่ยวกับธรรมชาติของมนุษย์มากกว่าการเขียนโปรแกรม บางทีเราจะทำในภายหลัง ...

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

ก่อนอื่นเราต้องเข้าใจอย่างชัดเจนว่าตัวชี้ คืออะไร

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

ขณะที่หลายคนได้ออกมาชี้: คำชี้เป็นเพียงชื่อพิเศษสำหรับสิ่งที่เป็นเพียงดัชนีและทำให้ไม่มีอะไรมากไปกว่าที่อื่น ๆจำนวน

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

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

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

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

    • การใช้งานบางอย่างยังคงมีความซับซ้อนมากขึ้นและจำนวนทั้งสิ้นของ 'ถนน' ไม่จำเป็นต้องรวมกับลำดับที่ต่อเนื่องกัน
    • เราจำเป็นต้องสามารถย่อยสลายทุกลิงก์ลำดับชั้นกลับไปเป็นองค์กรที่เรียบง่าย ยิ่งองค์กรมีความซับซ้อนมากเท่าไหร่เราก็ยิ่งต้องกระโดดมากขึ้นเท่านั้น แต่ก็ต้องทำเป็นไปได้ อันที่จริงสิ่งนี้ยังใช้กับ 'โหมดจริง' บน x86
    • มิฉะนั้นการทำแผนที่ของการเชื่อมโยงไปยังสถานที่จะไม่bijectiveเช่นการดำเนินการที่เชื่อถือได้ - ที่ระดับระบบ - ความต้องการว่ามันต้องเป็น
      • ที่อยู่หลายแห่งต้องไม่จับคู่กับตำแหน่งหน่วยความจำเอกพจน์และ
      • ที่อยู่เอกพจน์จะต้องไม่แมปไปยังตำแหน่งหน่วยความจำหลายแห่ง

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

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

สมมติว่ามีบ้านเพียง 20 หลังบนถนนที่เป็นเอกเทศของเรา นอกจากแกล้งทำเป็นว่าบางส่วนเข้าใจผิดหรือจิตวิญญาณ dyslexic ได้กำกับตัวอักษรหนึ่งที่สำคัญมากในการจำนวน 71 ตอนนี้เราสามารถขอให้ผู้ให้บริการของเราแฟรงก์ไม่ว่าจะมีเป็นเช่นที่อยู่และเขาจะเพียงและรายงานใจเย็น: ไม่มี เรายังสามารถคาดหวังให้เขาประเมินว่าไกลนอกถนนสถานที่แห่งนี้จะอยู่ถ้ามันไม่ได้มีอยู่: ประมาณ 2.5 เท่าไกลเกินกว่าที่สิ้นสุด สิ่งนี้จะไม่ทำให้เขาโกรธเคืองใด ๆ อย่างไรก็ตามหากเราต้องขอให้เขาส่งจดหมายนี้หรือรับสิ่งของจากสถานที่นั้นเขามีแนวโน้มที่จะค่อนข้างตรงไปตรงมาเกี่ยวกับความไม่พอใจของเขาและปฏิเสธที่จะปฏิบัติตาม

พอยน์เตอร์เป็นเพียงที่อยู่และที่อยู่เป็นเพียงตัวเลข

ตรวจสอบผลลัพธ์ต่อไปนี้:

void foo( void *p ) {
   printf(“%p\t%zu\t%d\n”, p, (size_t)p, p == (size_t)p);
}

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

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

  • เรากำลังคำนวณresult อย่างชัดเจนเพื่อความชัดเจนเท่านั้นและพิมพ์เพื่อบังคับให้คอมไพเลอร์คำนวณสิ่งที่มิฉะนั้นจะซ้ำซ้อนรหัสตาย
void foo( size_t *a, size_t *b ) {
   size_t result;
   result = (size_t)a;
   printf(“%zu\n”, result);
   result = a == b;
   printf(“%zu\n”, result);
   result = a < b;
   printf(“%zu\n”, result);
   result = a - b;
   printf(“%zu\n”, result);
}

แน่นอนว่าโปรแกรมนั้นมีรูปแบบไม่ดีเมื่อ a หรือ b ไม่ได้ถูกกำหนด (อ่าน: ไม่เริ่มต้นอย่างถูกต้อง ) ณ จุดที่ทำการทดสอบ แต่นั่นไม่เกี่ยวข้องกับส่วนนี้ของการสนทนาของเรา ตัวอย่างเหล่านี้มากเกินไปงบต่อไปนี้มีการรับประกัน - โดย 'มาตรฐาน' -เพื่อรวบรวมและทำงานไม่มีที่ติ แต่อย่างไรก็ตามใน -validity ของตัวชี้ใด ๆ ที่เกี่ยวข้อง

แต่ปัญหาเกิดขึ้นเมื่อตัวชี้ไม่ถูกต้องdereferenced เมื่อเราขอให้แฟรงค์ส่งหรือรับที่อยู่ที่ไม่ถูกต้องไม่มีอยู่จริง

รับตัวชี้ใด ๆ :

int *p;

ในขณะที่คำสั่งนี้จะต้องรวบรวมและเรียกใช้:

printf(“%p”, p);

... ตามที่ต้องการ:

size_t foo( int *p ) { return (size_t)p; }

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

printf(“%p”, *p);
size_t foo( int *p ) { return *p; }

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

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

int* validate( int *p, int *head, int *tail ) { 
    return p >= head && p <= tail ? p : NULL; 
}

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

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

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

คุณแต่เป็นโปรแกรมเมอร์อาจมีความรู้ดังกล่าว! และในบางกรณีจำเป็นต้องใช้ประโยชน์จากสิ่งนั้น

มีกำลังจึงตกอยู่ในสถานการณ์ที่แม้เรื่องนี้จะมีทั้งที่ถูกต้องและสมบูรณ์เหมาะสม

ในความเป็นจริงที่เป็นว่าสิ่งที่mallocตัวเองจะต้องทำภายในเมื่อเวลามาถึงพยายามผสานบล็อกยึด - ที่ส่วนใหญ่ของสถาปัตยกรรม เช่นเดียวกับที่เป็นจริงสำหรับตัวจัดสรรระบบปฏิบัติการเช่นที่อยู่เบื้องหลังว่าsbrk; ถ้ามากขึ้นอย่างเห็นได้ชัด , บ่อยในที่แตกต่างกันมากขึ้นหน่วยงานที่มากขึ้นอย่างยิ่ง - และมีความเกี่ยวข้องยังบนแพลตฟอร์มที่นี้mallocอาจจะไม่ และมีกี่คนที่ไม่ได้เขียนใน C?

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

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

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

ผลก็แค่พูดว่า: 'นอกเหนือจากจุดนี้คาวบอย : คุณอยู่ในตัวของคุณเอง ... '

อาจารย์ของคุณกำลังพยายามแสดงให้เห็นถึงความแตกต่างของคุณ

สังเกตว่าพวกเขาใส่ใจอย่างมากในการสร้างตัวอย่างของพวกเขา และมันเปราะยังไง โดยจดที่อยู่ของaใน

p[0].p0 = &a;

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

เพียงแค่เปลี่ยนบรรทัดนี้:

char a = 0;

สำหรับสิ่งนี้:

char a = 1;  // or ANY other value than 0

ทำให้เกิดลักษณะการทำงานของโปรแกรมที่จะกลายเป็นไม่ได้กำหนด อย่างน้อยที่สุดคำตอบแรกจะเป็น 1; แต่ปัญหาคือสิ่งที่น่ากลัวยิ่งกว่า

ตอนนี้รหัสกำลังเชิญภัยพิบัติ

ในขณะที่ยังคงใช้งานได้ดีและสอดคล้องกับมาตรฐานแต่ตอนนี้มันมีรูปแบบไม่ดีและถึงแม้ว่าจะแน่ใจว่าได้รวบรวม แต่อาจล้มเหลวในการดำเนินการในพื้นที่ต่างๆ สำหรับตอนนี้มีหลายปัญหา - ไม่มีซึ่งคอมไพเลอร์คือสามารถที่จะรับรู้

strcpyจะเริ่มต้นตามที่อยู่ของaและดำเนินการต่อไปนี้เพื่อกิน - และถ่ายโอน - ไบต์หลังไบต์จนกว่าจะพบ null

p1ชี้ได้รับการเริ่มต้นได้ที่บล็อกของตรง10ไบต์

  • หากaเกิดขึ้นที่ปลายของบล็อกและกระบวนการไม่สามารถเข้าถึงสิ่งต่อไปนี้การอ่าน p0 [1] ครั้งต่อไปจะทำให้เกิด segfault สถานการณ์นี้ไม่น่าจะเกิดขึ้นกับสถาปัตยกรรม x86 แต่เป็นไปได้

  • หากสามารถเข้าถึงพื้นที่ที่อยู่นอกเหนือจากที่อยู่a จะไม่เกิดข้อผิดพลาดในการอ่าน แต่โปรแกรมจะไม่ถูกบันทึกจากความโชคร้าย

  • หากมีศูนย์เกิดขึ้นภายในสิบเริ่มต้นที่ที่อยู่ของaมันอาจยังคงอยู่รอดเพราะจากนั้นstrcpyจะหยุดและอย่างน้อยเราจะไม่ได้รับการละเมิดการเขียน

  • ถ้ามันจะไม่โทษฐานสำหรับการอ่านผิดปกติ แต่ไม่มีศูนย์ byteเกิดขึ้นในช่วง 10 นี้strcpyจะดำเนินต่อไปและพยายามที่จะเขียนmallocเกินบล็อกจัดสรรโดย

    • หากพื้นที่นี้ไม่ได้เป็นเจ้าของโดยกระบวนการ segfault ควรจะถูกเรียกใช้ทันที

    • ยังคงหายนะมากขึ้น - และลึกซึ้ง --- สถานการณ์เกิดขึ้นเมื่อบล็อกต่อไปนี้จะเป็นเจ้าของโดยกระบวนการสำหรับข้อผิดพลาดแล้วไม่สามารถถูกตรวจพบว่าไม่มีสัญญาณสามารถยกและดังนั้นจึงอาจ'ปรากฏ' ยัง 'งาน' , ในขณะที่มันจะถูกเขียนทับข้อมูลอื่น ๆ โครงสร้างการจัดการของผู้จัดสรรหรือแม้แต่รหัส (ในสภาพแวดล้อมการทำงานบางอย่าง)

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

อย่างไรก็ตามโปรแกรมจะต้องรวบรวมเพราะมันยังคงสมบูรณ์และเป็นไปตามมาตรฐาน C.

ข้อผิดพลาดประเภทนี้ไม่มีมาตรฐานและไม่มีคอมไพเลอร์สามารถป้องกันไม่ระวัง ฉันคิดว่านั่นคือสิ่งที่พวกเขาตั้งใจจะสอนคุณ

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

ความสะดวกในการพกพาและความเป็นสากลที่จะนำเสนอเป็นข้อพิจารณาแยกต่างหากโดยพื้นฐานและทุกสิ่งที่มาตรฐานพยายามหาที่อยู่:

เอกสารนี้ระบุรูปแบบและกำหนดความหมายของโปรแกรมการแสดงออกในการเขียนโปรแกรมภาษาซีใช้ในจุดประสงค์คือการส่งเสริมการพกพาความน่าเชื่อถือ, การบำรุงรักษาและการดำเนินการที่มีประสิทธิภาพของโปรแกรมภาษา C บนความหลากหลายของการคำนวณระบบ

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

สรุป:

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

สิ่งนี้ไม่จริงการเขียนโปรแกรมตามที่เรารู้- และรักมัน -คงเป็นไปไม่ได้


3
คำตอบนี้ไม่ถูกต้องอย่างน่าเสียดาย คุณไม่สามารถให้เหตุผลอะไรเกี่ยวกับพฤติกรรมที่ไม่ได้กำหนด ไม่จำเป็นต้องทำการเปรียบเทียบที่ระดับเครื่อง
Antti Haapala

6
Ghii ไม่จริง ถ้าคุณดูที่ C11 ภาคผนวก J และ 6.5.8 การเปรียบเทียบตัวเองคือ UB การอ้างอิงเป็นปัญหาแยกต่างหาก
paxdiablo

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

2
@Ghii, ภาคผนวก J (บิตที่ฉันพูดถึง) คือรายการของสิ่งที่ไม่ได้กำหนดพฤติกรรมดังนั้นฉันไม่แน่ใจว่าวิธีการที่สนับสนุนอาร์กิวเมนต์ของคุณ :-) 6.5.8 เรียกการเปรียบเทียบอย่างชัดเจนว่าเป็น UB สำหรับความคิดเห็นของคุณกับ supercat ไม่มีการเปรียบเทียบเกิดขึ้นเมื่อคุณพิมพ์ตัวชี้ดังนั้นคุณอาจถูกต้องว่ามันจะไม่ผิดพลาด แต่นั่นไม่ใช่สิ่งที่ OP ถามเกี่ยวกับ 3.4.3เป็นส่วนที่คุณควรพิจารณาด้วย: มันนิยาม UB ว่าเป็นพฤติกรรม "ซึ่งมาตรฐานสากลนี้ไม่มีข้อกำหนด"
paxdiablo

3
@GhiiVelte คุณยังคงระบุสิ่งที่ผิดอย่างชัดเจนแม้จะมีการชี้ไปที่คุณ ใช่ตัวอย่างข้อมูลที่คุณโพสต์ต้องรวบรวมแต่ความขัดแย้งของคุณที่ทำงานโดยไม่มีการผูกปมนั้นไม่ถูกต้อง ฉันขอแนะนำให้คุณอ่านมาตรฐานโดยเฉพาะอย่างยิ่ง (ในกรณีนี้) C11 6.5.6/9โปรดจำไว้ว่าคำว่า "จะ" หมายถึงข้อกำหนด L "เมื่อตัวชี้สองตัวถูกลบออกทั้งสองจะชี้ไปที่องค์ประกอบของวัตถุอาร์เรย์เดียวกันหรือผ่านมาหนึ่งครั้งสุดท้าย องค์ประกอบของวัตถุอาร์เรย์ "
paxdiablo

-5

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

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


2
สแต็กคำปรากฏขึ้นเป็นศูนย์อย่างแน่นอนในมาตรฐาน C11 และพฤติกรรมที่ไม่ได้กำหนดหมายถึงสิ่งที่สามารถเกิดขึ้นได้ (รวมถึงความผิดพลาดของโปรแกรม)
paxdiablo

1
@paxdiablo ฉันว่ามันทำหรือไม่
Nickelpro

2
คุณพูดถึงตัวแปรที่จัดสรรสแต็ก ไม่มีมาตรฐานในสแต็กนั่นเป็นเพียงรายละเอียดการใช้งาน ปัญหาที่ร้ายแรงยิ่งกว่ากับคำตอบนี้คือการโต้แย้งที่คุณสามารถเปรียบเทียบตัวชี้โดยไม่มีโอกาสตก - นั่นเป็นเพียงความผิด
paxdiablo

1
@nickelpro: หากมีความประสงค์ที่จะเขียนโค้ดที่เข้ากันได้กับเครื่องมือเพิ่มประสิทธิภาพใน gcc และ clang คุณจำเป็นต้องกระโดดผ่านห่วงที่โง่มาก เครื่องมือเพิ่มประสิทธิภาพทั้งสองจะแสวงหาโอกาสอย่างจริงจังในการดึงข้อสรุปเกี่ยวกับสิ่งที่ตัวชี้จะเข้าถึงได้เมื่อใดก็ตามที่มีวิธีใดก็ตามที่มาตรฐานสามารถบิดเพื่อแสดงให้เห็นถึงความชอบธรรม (และบางครั้งเมื่อไม่มี) ป.ร. ให้ไว้int x[10],y[10],*p;ถ้ารหัสประเมินy[0]แล้วประเมินp>(x+5)และเขียน*pโดยไม่ต้องแก้ไขpในระหว่างกาลและประเมินy[0]อีกครั้งในที่สุด...
supercat

1
นิกเกิลโปรยอมรับที่จะไม่เห็นด้วย แต่คำตอบของคุณยังผิด ฉันเปรียบวิธีที่คุณใช้กับคนที่ใช้(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')แทนisalpha()เพราะการใช้สติอะไรที่จะทำให้ตัวละครเหล่านั้นไม่ต่อเนื่อง? บรรทัดล่างคือว่าแม้ว่าจะไม่มีการใช้งานที่คุณรู้ว่ามีปัญหาคุณควรจะเขียนโค้ดให้ได้มาตรฐานมากที่สุดถ้าคุณให้ความสำคัญกับการพกพา ฉันขอขอบคุณฉลาก "มาตรฐาน maven" แม้ว่าขอบคุณสำหรับสิ่งนั้น ผมอาจจะใส่ใน CV ของฉัน :-)
paxdiablo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.