Apakah aman untuk menyimpan penunjuk di luar batas tanpa merusaknya? [duplikat]
Apakah aman di C untuk menyimpan pointer di luar batas (tanpa mendereferensi) untuk aritmatika lebih lanjut?
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;
}
}
Saya membayangkan beberapa kasus ekstrim, jika alamatnya adalah memori pertama atau yang terakhir ...
Jawaban
Memindahkan penunjuk ke satu elemen melewati elemen terakhir diperbolehkan, tetapi bergerak lebih jauh atau bergerak sebelum elemen pertama tidak diperbolehkan.
Kutipan dari N1570 6.5.6 Operator aditif (poin 8):
Ketika ekspresi yang memiliki tipe integer ditambahkan atau dikurangi dari pointer, hasilnya memiliki tipe operand pointer. Jika operan penunjuk menunjuk ke elemen objek larik, dan larik cukup besar, hasilnya menunjuk ke elemen yang diimbangi dari elemen asli sedemikian rupa sehingga perbedaan subskrip elemen larik yang dihasilkan dan asli sama dengan ekspresi bilangan bulat. Dengan kata lain, jika ekspresi P menunjuk ke elemen ke-i dari objek array, ekspresi (P) + N (ekuivalen, N + (P)) dan (P) -N (di mana N memiliki nilai n) titik ke, masing-masing, elemen i + n-th dan i − n-th dari objek array, asalkan ada. Selain itu, jika ekspresi P menunjuk ke elemen terakhir dari objek array, ekspresi (P) +1 poin satu melewati elemen terakhir dari objek array, dan jika ekspresi Q menunjuk satu melewati elemen terakhir dari objek array, ekspresi (Q) -1 menunjuk ke elemen terakhir dari objek array. Jika kedua operan penunjuk dan hasil menunjuk ke elemen objek larik yang sama, atau melewati elemen terakhir dari objek larik, evaluasi tidak akan menghasilkan luapan; jika tidak, perilaku tidak ditentukan. Jika hasil menunjuk satu melewati elemen terakhir dari objek array, itu tidak akan digunakan sebagai operan dari operator unary * yang dievaluasi.
Sebuah pointer dapat menunjuk ke satu elemen melewati elemen terakhir dari array, dan aritmatika pointer dapat dilakukan antara pointer itu dan pointer ke elemen array.
Penunjuk seperti itu tidak dapat dideferensiasi, tetapi dapat digunakan dalam aritmatika penunjuk. Misalnya, berikut ini valid:
char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff); // prints 5
Sebuah pointer tidak boleh menunjuk sebelum elemen pertama.
Ini dijelaskan di bagian 6.5.6p8 dari standar C :
Ketika ekspresi yang memiliki tipe integer ditambahkan atau dikurangi dari pointer, hasilnya memiliki tipe operand pointer. Jika operan penunjuk menunjuk ke elemen objek larik, dan larik cukup besar, hasilnya menunjuk ke elemen yang diimbangi dari elemen asli sedemikian rupa sehingga perbedaan subskrip elemen larik yang dihasilkan dan asli sama dengan ekspresi bilangan bulat. Dengan kata lain, jika ekspresi
P
menunjuk ke elemen ke- i dari sebuah objek larik, ekspresi(P)+N
(ekuivalen,N+(P)
) dan(P)-N
(di manaN
memiliki nilai n ) masing-masing menunjuk ke, i + n -th dan i − n -th elemen objek larik, asalkan ada. Selain itu, jika ekspresiP
menunjuk ke elemen terakhir dari objek array, ekspresi(P)+1
menunjuk satu melewati elemen terakhir dari objek array, dan jika ekspresiQ
menunjuk satu melewati elemen terakhir dari objek array, ekspresi(Q)-1
menunjuk ke elemen terakhir. dari objek array. Jika kedua operan penunjuk dan hasil menunjuk ke elemen objek larik yang sama, atau melewati elemen terakhir dari objek larik, evaluasi tidak akan menghasilkan luapan; jika tidak, perilaku tidak ditentukan. Jika hasil menunjuk satu melewati elemen terakhir dari objek larik, itu tidak akan digunakan sebagai operan dari*
operator unary yang dievaluasi.
Perhatikan bahwa bagian tebal yang menyatakan bahwa penunjuk dapat dibuat untuk menunjuk ke satu elemen melewati akhir larik, dan tidak ada yang memungkinkan untuk menunjuk ke titik mana pun sebelum dimulainya larik.
Seperti yang ditunjukkan orang lain, Anda diizinkan untuk menunjukkan satu masa lalu . Namun perlu diingat bahwa TIDAK diperbolehkan untuk menunjuk satu elemen sebelum yang pertama. Jadi, Anda mungkin ingin berhati-hati jika menulis algoritme yang melintasi array secara terbalik. Karena cuplikan ini tidak valid:
void foo(int *arr, int *end) {
while(end-- != arr) { // Ouch, bad idea...
// Code
}
// Here, end has the value arr[-1]
}
Itu karena, ketika end
menunjuk pada elemen yang sama dengan arr
, kondisinya akan menjadi salah, tetapi setelah itu, end
dikurangi sekali lagi dan akan menunjuk ke satu elemen sebelum larik, sehingga memunculkan perilaku tidak terdefinisi.
Perhatikan bahwa selain itu, kodenya berfungsi dengan baik. Untuk memperbaiki bug, Anda dapat melakukan ini sebagai gantinya:
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
}
Kode dalam loop akan bekerja persis seperti sebelumnya. Satu-satunya perbedaan adalah bahwa end
tidak akan berkurang terakhir kali.