Bir işaretçiyi referanstan kaldırmadan sınırların dışında tutmak güvenli midir? [çiftleme]
Daha fazla aritmetik için bir işaretçiyi sınırların dışında tutmak (referansını kaldırmadan) C'de güvenli midir?
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;
}
}
Adres hafızanın ilki mi yoksa sonuncusu mu, bazı uç durumlar hayal ediyorum ...
Yanıtlar
İmlecin son öğeden sonraki bir öğeye taşınmasına izin verilir, ancak daha fazla veya ilk öğeden önce hareket etmeye izin verilmez.
N1570 6.5.6 Katkı operatörlerinden alıntı (nokta 8):
Bir işaretçiye tamsayı türüne sahip bir ifade eklendiğinde veya çıkarıldığında, sonuç, işaretçi işleneninin türüne sahiptir. İşaretçi işleneni, bir dizi nesnesinin bir öğesini işaret ederse ve dizi yeterince büyükse, sonuç, ortaya çıkan ve orijinal dizi öğelerinin alt simgelerinin farkı tamsayı ifadesine eşit olacak şekilde, orijinal öğeden uzaktaki bir öğeye işaret eder. Diğer bir deyişle, P ifadesi bir dizi nesnesinin i'inci elemanına işaret ederse, (P) + N (eşdeğer olarak, N + (P)) ve (P) -N (burada N, n değerine sahiptir) sırasıyla, mevcut olmaları koşuluyla dizi nesnesinin i + n-th ve i − n-inci öğelerine. Ayrıca, P ifadesi bir dizi nesnesinin son elemanına işaret ederse, (P) +1 ifadesi dizi nesnesinin son elemanının bir ötesine işaret ederse ve Q ifadesi bir dizi nesnesinin son elemanının bir ötesine işaret ederse, (Q) -1 ifadesi, dizi nesnesinin son elemanına işaret eder. Hem işaretçi işleneni hem de sonuç, aynı dizi nesnesinin öğelerini gösteriyorsa veya dizi nesnesinin son öğesini geçerse, değerlendirme bir taşma oluşturmayacaktır; aksi takdirde davranış tanımsızdır. Sonuç, dizi nesnesinin son elemanını bir geçiyorsa, değerlendirilen tekli * operatörün işleneni olarak kullanılmamalıdır.
Bir işaretçi, dizinin son elemanını geçen bir elemanı işaret edebilir ve bu işaretçi ile dizinin bir elemanına bir işaretçi arasında işaretçi aritmetiği yapılabilir.
Böyle bir gösterici referans alınamaz, ancak işaretçi aritmetiğinde kullanılabilir. Örneğin, aşağıdaki geçerlidir:
char arr[10];
char *p1, *p2;
p1 = arr + 10;
p2 = arr + 5;
int diff = p1 - p2;
printf("diff=%d\n", diff); // prints 5
Bir işaretçi , ilk öğenin önünü gösteremez .
Bu, C standardının 6.5.6p8 bölümünde açıklanmıştır :
Bir işaretçiye tamsayı türüne sahip bir ifade eklendiğinde veya çıkarıldığında, sonuç, işaretçi işleneninin türüne sahiptir. İşaretçi işleneni bir dizi nesnesinin bir öğesine işaret ederse ve dizi yeterince büyükse, sonuç, ortaya çıkan ve orijinal dizi öğelerinin alt simgelerinin farkı tamsayı ifadesine eşit olacak şekilde orijinal öğeden uzaklığı olan bir öğeye işaret eder. Diğer bir deyişle, sentezleme ise
P
işaret ı bir dizi nesne, ifadelerin inci elemanı(P)+N
(eşit biçimde,N+(P)
) ve(P)-N
(N
değerine sahip olduğu N ), sırasıyla, bir nokta i + n 'inci ve i-n -inci mevcut olmaları koşuluyla dizi nesnesinin elemanları. Ayrıca, ifadeP
bir dizi nesnesinin son öğesini işaret ederse, ifade dizi nesnesinin(P)+1
son öğesiniQ
bir geçmişe işaret ederse ve ifade bir dizi nesnesinin son öğesini bir geçmişse, ifade(Q)-1
son öğeyi gösterir dizi nesnesinin. Hem işaretçi işleneni hem de sonuç, aynı dizi nesnesinin öğelerini gösteriyorsa veya dizi nesnesinin son öğesini geçerse, değerlendirme bir taşma oluşturmayacaktır; aksi takdirde davranış tanımsızdır. Sonuç, dizi nesnesinin son elemanını bir geçiyorsa*
, değerlendirilen tekli bir operatörün işleneni olarak kullanılmamalıdır .
Bir göstericinin dizinin sonunu geçen bir öğeyi gösterecek şekilde oluşturulabileceğini belirten kalın bölüme ve dizinin başlangıcından önceki herhangi bir noktaya işaret etmeye izin veren hiçbir şey olmadığına dikkat edin.
Diğerlerinin de belirttiği gibi, bir geçmişi göstermenize izin verilir . Ancak bir öğenin ilkinden önce işaret edilmesine izin verilmediğini unutmayın . Dolayısıyla, dizileri geriye doğru hareket ettiren algoritmalar yazarsanız dikkatli olmak isteyebilirsiniz. Çünkü bu pasaj geçersiz:
void foo(int *arr, int *end) {
while(end-- != arr) { // Ouch, bad idea...
// Code
}
// Here, end has the value arr[-1]
}
Bunun nedeni de zaman end
ile aynı eleman noktasında, arr
koşul yanlış olacaktır, ancak, daha sonra bu, end
bir daha azaltıldığı ve bir eleman işaret edecek önce bu şekilde tanımlanmamış davranışı çağırarak, bir dizi.
Bunun dışında kodun iyi çalıştığını unutmayın. Hatayı düzeltmek için bunun yerine şunu yapabilirsiniz:
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
}
Döngüdeki kod tam olarak eskisi gibi çalışacaktır. Tek fark, end
son kez azaltılmayacak olmasıdır.