ปลอดภัยหรือไม่ที่จะทำให้ตัวชี้อยู่นอกขอบเขตโดยไม่ต้องยกเลิกการอ้างอิง [ซ้ำ]
ใน 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;
}
}
ฉันนึกภาพบางกรณีที่รุนแรงถ้าที่อยู่เป็นครั้งแรกของความทรงจำหรือสุดท้าย ...
คำตอบ
อนุญาตให้ย้ายตัวชี้ไปยังองค์ประกอบหนึ่งผ่านองค์ประกอบสุดท้ายได้ แต่ไม่อนุญาตให้เลื่อนต่อไปหรือย้ายก่อนองค์ประกอบแรก
อ้างจาก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 ชี้ไปยังองค์ประกอบสุดท้ายของวัตถุอาร์เรย์ ถ้าทั้งตัวถูกดำเนินการตัวชี้และผลลัพธ์ชี้ไปที่องค์ประกอบของวัตถุอาร์เรย์เดียวกันหรืออย่างใดอย่างหนึ่งเลยองค์ประกอบสุดท้ายของวัตถุอาร์เรย์การประเมินจะไม่ทำให้เกิดการล้น มิฉะนั้นจะไม่มีการกำหนดพฤติกรรม หากผลลัพธ์ชี้ไปที่องค์ประกอบสุดท้ายของออบเจ็กต์อาร์เรย์จะไม่ใช้เป็นตัวถูกดำเนินการของตัวดำเนินการยูนารี * ที่ได้รับการประเมิน
ตัวชี้อาจชี้ไปที่องค์ประกอบหนึ่งผ่านองค์ประกอบสุดท้ายของอาร์เรย์และการคำนวณทางคณิตศาสตร์ของตัวชี้อาจทำได้ระหว่างตัวชี้นั้นและตัวชี้ไปยังองค์ประกอบของอาร์เรย์
ตัวชี้ดังกล่าวไม่สามารถอ้างถึงได้ แต่สามารถใช้ในการคำนวณทางคณิตศาสตร์ของตัวชี้ได้ ตัวอย่างเช่นข้อมูลต่อไปนี้ใช้ได้:
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
จะชี้ไปที่องค์ประกอบสุดท้าย ของวัตถุอาร์เรย์ ถ้าทั้งตัวถูกดำเนินการตัวชี้และผลลัพธ์ชี้ไปที่องค์ประกอบของวัตถุอาร์เรย์เดียวกันหรืออย่างใดอย่างหนึ่งเลยองค์ประกอบสุดท้ายของวัตถุอาร์เรย์การประเมินจะไม่ทำให้เกิดการล้น มิฉะนั้นจะไม่มีการกำหนดพฤติกรรม หากผลลัพธ์ชี้ไปที่องค์ประกอบสุดท้ายของวัตถุอาร์เรย์จะไม่ถูกใช้เป็นตัวถูกดำเนินการของตัวดำเนิน*
การยูนารีที่ได้รับการประเมิน
โปรดสังเกตว่าส่วนที่เป็นตัวหนาซึ่งระบุว่าตัวชี้อาจถูกสร้างขึ้นเพื่อชี้ไปยังองค์ประกอบหนึ่งที่อยู่หลังจุดสิ้นสุดของอาร์เรย์และไม่มีสิ่งใดที่อนุญาตให้ชี้ไปที่จุดใดก็ได้ก่อนที่จะเริ่มอาร์เรย์
ตามที่คนอื่นชี้ให้เห็นคุณสามารถชี้อดีตได้ แต่อย่าลืมว่าไม่อนุญาตให้ชี้องค์ประกอบหนึ่งก่อนองค์ประกอบแรก ดังนั้นคุณอาจต้องระวังหากคุณเขียนอัลกอริทึมที่ข้ามอาร์เรย์ไปข้างหลัง เนื่องจากข้อมูลโค้ดนี้ไม่ถูกต้อง:
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
จะไม่ลดลงในครั้งสุดท้าย