ปลอดภัยหรือไม่ที่จะทำให้ตัวชี้อยู่นอกขอบเขตโดยไม่ต้องยกเลิกการอ้างอิง [ซ้ำ]

Jan 10 2021

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

void f(int *array)
{
    int *i = array - 1;  // OOB

    while(...) {
        ++i;
        ...
    }
}

void g(int *array, int *end /* past-the-end pointer: OOB */)
{
    while(array != end) {
        ...
        ++array;
    }
}

ฉันนึกภาพบางกรณีที่รุนแรงถ้าที่อยู่เป็นครั้งแรกของความทรงจำหรือสุดท้าย ...

คำตอบ

9 MikeCAT Jan 10 2021 at 07:31

อนุญาตให้ย้ายตัวชี้ไปยังองค์ประกอบหนึ่งผ่านองค์ประกอบสุดท้ายได้ แต่ไม่อนุญาตให้เลื่อนต่อไปหรือย้ายก่อนองค์ประกอบแรก

อ้างจากN1570 6.5.6 ตัวดำเนินการเพิ่มเติม (จุดที่ 8):

เมื่อนิพจน์ที่มีชนิดจำนวนเต็มถูกเพิ่มหรือลบออกจากตัวชี้ผลลัพธ์จะมีชนิดของตัวถูกดำเนินการของตัวชี้ หากตัวถูกดำเนินการชี้ไปที่องค์ประกอบของออบเจ็กต์อาร์เรย์และอาร์เรย์มีขนาดใหญ่พอผลลัพธ์จะชี้ไปที่องค์ประกอบที่หักล้างจากองค์ประกอบดั้งเดิมเพื่อให้ความแตกต่างของตัวห้อยขององค์ประกอบอาร์เรย์ที่เป็นผลลัพธ์และองค์ประกอบดั้งเดิมเท่ากับนิพจน์จำนวนเต็ม กล่าวอีกนัยหนึ่งถ้านิพจน์ P ชี้ไปที่องค์ประกอบ i-th ของอ็อบเจ็กต์อาร์เรย์นิพจน์ (P) + N (เทียบเท่า N + (P)) และ (P) -N (โดยที่ N มีค่า n) จุด ถึงตามลำดับองค์ประกอบ i + n-th และ i − n-th ของวัตถุอาร์เรย์หากมีอยู่ ยิ่งไปกว่านั้นหากนิพจน์ P ชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์นิพจน์ (P) +1 จะชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์และหากนิพจน์ Q ชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์ นิพจน์ (Q) -1 ชี้ไปยังองค์ประกอบสุดท้ายของวัตถุอาร์เรย์ ถ้าทั้งตัวถูกดำเนินการตัวชี้และผลลัพธ์ชี้ไปที่องค์ประกอบของวัตถุอาร์เรย์เดียวกันหรืออย่างใดอย่างหนึ่งเลยองค์ประกอบสุดท้ายของวัตถุอาร์เรย์การประเมินจะไม่ทำให้เกิดการล้น มิฉะนั้นจะไม่มีการกำหนดพฤติกรรม หากผลลัพธ์ชี้ไปที่องค์ประกอบสุดท้ายของออบเจ็กต์อาร์เรย์จะไม่ใช้เป็นตัวถูกดำเนินการของตัวดำเนินการยูนารี * ที่ได้รับการประเมิน

3 dbush Jan 10 2021 at 07:32

ตัวชี้อาจชี้ไปที่องค์ประกอบหนึ่งผ่านองค์ประกอบสุดท้ายของอาร์เรย์และการคำนวณทางคณิตศาสตร์ของตัวชี้อาจทำได้ระหว่างตัวชี้นั้นและตัวชี้ไปยังองค์ประกอบของอาร์เรย์

ตัวชี้ดังกล่าวไม่สามารถอ้างถึงได้ แต่สามารถใช้ในการคำนวณทางคณิตศาสตร์ของตัวชี้ได้ ตัวอย่างเช่นข้อมูลต่อไปนี้ใช้ได้:

char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff);   // prints 5

ตัวชี้ต้องไม่ชี้ก่อนองค์ประกอบแรก

สิ่งนี้สะกดในส่วน 6.5.6p8 ของมาตรฐาน C :

เมื่อนิพจน์ที่มีชนิดจำนวนเต็มถูกเพิ่มหรือลบออกจากตัวชี้ผลลัพธ์จะมีชนิดของตัวถูกดำเนินการของตัวชี้ หากตัวถูกดำเนินการชี้ไปที่องค์ประกอบของออบเจ็กต์อาร์เรย์และอาร์เรย์มีขนาดใหญ่พอผลลัพธ์จะชี้ไปที่องค์ประกอบที่หักล้างจากองค์ประกอบดั้งเดิมเพื่อให้ความแตกต่างของตัวห้อยขององค์ประกอบอาร์เรย์ที่เป็นผลลัพธ์และองค์ประกอบดั้งเดิมเท่ากับนิพจน์จำนวนเต็ม กล่าวอีกนัยหนึ่งถ้านิพจน์Pชี้ไปที่องค์ประกอบi -th ของอ็อบเจ็กต์อาร์เรย์นิพจน์(P)+N(เทียบเท่าN+(P)) และ(P)-N(ที่Nมีค่าn ) ชี้ไปตามลำดับi + n -th และi − n -th องค์ประกอบของวัตถุอาร์เรย์หากมีอยู่ ยิ่งไปกว่านั้นหากนิพจน์Pชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์นิพจน์จะ(P)+1ชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์และหากนิพจน์Qชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์นิพจน์(Q)-1จะชี้ไปที่องค์ประกอบสุดท้าย ของวัตถุอาร์เรย์ ถ้าทั้งตัวถูกดำเนินการตัวชี้และผลลัพธ์ชี้ไปที่องค์ประกอบของวัตถุอาร์เรย์เดียวกันหรืออย่างใดอย่างหนึ่งเลยองค์ประกอบสุดท้ายของวัตถุอาร์เรย์การประเมินจะไม่ทำให้เกิดการล้น มิฉะนั้นจะไม่มีการกำหนดพฤติกรรม หากผลลัพธ์ชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์จะไม่ถูกใช้เป็นตัวถูกดำเนินการของตัวดำเนิน*การยูนารีที่ได้รับการประเมิน

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

klutt Jan 10 2021 at 08:19

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

void foo(int *arr, int *end) {
    while(end-- != arr) { // Ouch, bad idea...
        // Code
    }
    // Here, end has the value arr[-1]
}

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

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

void foo(int *arr, int *end) {
    while(end != arr) { 
        end--; // Move end-- to inside the loop, in the very beginning

        // Code
    }

    // And here, end is equal to arr, which is perfectly fine
}

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