... hanya mengurangi pointer di luar rentang yang dialokasikan tampaknya sangat samar bagi saya. Apakah perilaku "diizinkan" ini dalam C?
Diizinkan? Iya. Ide bagus? Tidak biasanya.
C adalah singkatan untuk bahasa assembly, dan dalam bahasa assembly tidak ada pointer, hanya alamat memori. Pointer C adalah alamat memori yang memiliki perilaku tambahan yang bertambah atau berkurang berdasarkan ukuran yang mereka tunjukkan ketika dikenakan aritmatika. Ini membuat berikut ini baik-baik saja dari perspektif sintaksis:
double *p = (double *)0xdeadbeef;
--p; // p == 0xdeadbee7, assuming sizeof(double) == 8.
double d = p[0];
Array sebenarnya bukan benda dalam C; mereka hanya petunjuk ke rentang memori yang berdekatan yang berperilaku seperti array. The []
operator adalah singkatan untuk melakukan aritmetik pointer dan dereferencing, sehingga a[x]
benar-benar berarti *(a + x)
.
Ada alasan yang sah untuk melakukan hal di atas, seperti beberapa perangkat I / O yang memiliki beberapa pasangan yang double
dipetakan ke 0xdeadbee7
dan 0xdeadbeef
. Sangat sedikit program yang perlu melakukan itu.
Saat Anda membuat alamat sesuatu, seperti dengan menggunakan &
operator atau panggilan malloc()
, Anda ingin menjaga pointer asli tetap utuh sehingga Anda tahu bahwa apa yang ditunjukkannya sebenarnya sesuatu yang valid. Mengurangkan penunjuk berarti bahwa sedikit kode yang salah dapat mencoba mereduksikannya, mendapatkan hasil yang salah, mengalahkan sesuatu atau, tergantung pada lingkungan Anda, melakukan pelanggaran segmentasi. Hal ini terutama berlaku untuk malloc()
, karena Anda telah membebani panggilan siapa pun free()
untuk mengingat untuk melewati nilai asli dan bukan versi yang diubah yang akan menyebabkan semua kehilangan.
Jika Anda membutuhkan array berbasis 1 di C, Anda dapat melakukannya dengan aman dengan mengorbankan alokasi satu elemen tambahan yang tidak akan pernah digunakan:
double *array_create(size_t size) {
// Wasting one element, so don't allow it to be full-sized
assert(size < SIZE_MAX);
return malloc((size+1) * sizeof(double));
}
inline double array_index(double *array, size_t index) {
assert(array != NULL);
assert(index >= 1); // This is a 1-based array
return array[index];
}
Perhatikan bahwa ini tidak melakukan apa pun untuk melindungi dari melampaui batas atas, tetapi itu cukup mudah untuk ditangani.
Tambahan:
Beberapa bab dan ayat dari draft C99 (maaf, hanya itu yang bisa saya tautkan):
§6.5.2.1.1 mengatakan bahwa ekspresi kedua ("lainnya") yang digunakan dengan operator subskrip adalah tipe integer. -1
adalah bilangan bulat, dan itu membuat p[-1]
valid dan karenanya juga membuat pointer &(p[-1])
valid. Ini tidak menyiratkan bahwa mengakses memori di lokasi itu akan menghasilkan perilaku yang ditentukan, tetapi pointer masih merupakan pointer yang valid.
§6.5.2.2 mengatakan bahwa operator subkrip array mengevaluasi setara dengan menambahkan nomor elemen ke pointer, oleh karena p[-1]
itu setara dengan *(p + (-1))
. Masih valid, tetapi mungkin tidak menghasilkan perilaku yang diinginkan.
§6.5.6.8 mengatakan (penekanan milikku):
Ketika ekspresi yang memiliki tipe integer ditambahkan atau dikurangi dari sebuah pointer, hasilnya memiliki tipe operan pointer.
... jika ekspresi P
menunjuk ke i
elemen -th dari objek array, ekspresi (P)+N
(ekuivalen, N+(P)
) dan (P)-N
(di mana N
memiliki nilai n
) menunjuk ke, masing-masing, elemen i+n
-th dan
i−n
-th dari objek array, asalkan ada .
Ini berarti bahwa hasil dari aritmatika pointer harus menunjuk pada elemen dalam array. Tidak dikatakan bahwa aritmatika harus dilakukan sekaligus. Karena itu:
double a[20];
// This points to element 9 of a; behavior is defined.
double d = a[-1 + 10];
double *p = a - 1; // This is just a pointer. No dereferencing.
double e = p[0]; // Does not point at any element of a; behavior is undefined.
double f = p[1]; // Points at element 0 of a; behavior is defined.
Apakah saya merekomendasikan melakukan hal-hal seperti ini? Saya tidak, dan jawaban saya menjelaskan alasannya.