Segmentasyon hatalarının ortak nedenlerinin kesin listesi

not: büyük ölçüde aynı olan çok sayıda segfault sorunuz var cevaplar, bu yüzden onları kanonik bir soruya daraltmaya çalışıyorum. tanımsız başvuru için var.

ne bir bölümleme hatası kapsayan bir sorunuz olmasına rağmen , ne kapsar , ancak birçok nedeni listelemek değil. En iyi cevap "birçok sebep var" diyor ve sadece bir tane listeliyor, ve diğer cevapların çoğu herhangi bir nedeni listelemiyor.

her şeyden önce, segfaults almak için tüm ortak nedenleri (ve daha sonra bazı) listeleyen bu konuda iyi organize edilmiş topluluk wiki ye ihtiyacımız olduğuna inanıyorum. Amaç, cevabın feragatnamesinde belirtildiği gibi hata ayıklamaya yardımcı olmaktır.

bir segmentasyon hatasının ne olduğunu biliyorum, ancak genellikle neye benzediğini bilmeden kodda bulunması zor olabilir. Her ne kadar, hiç şüphesiz, çok fazla yorucu listelemek için, segmentasyon hataları en yaygın nedenleri nelerdir C ve C++?

44
tarihinde sordu Community 2015-10-10 00:31:42
kaynak

1 ответов

uyarısı!

aşağıdakiler potansiyel segmentasyon hatası nedenleri. tüm nedenleri listelemek neredeyse imkansızdır. Bu listenin amacı mevcut bir segfault teşhis yardımcı olmaktır.

segmentasyon hataları ve tanımsız davranış arasındaki ilişki yeterince stresli olamaz! Tüm aşağıda segmentasyon hatası oluşturabilen durumlar teknik olarak tanımlanmamış davranışlardır. bu, veya yapabilecekleri anlamına gelir, sadece segfault değil-bir zamanlar USENET'TE söylediği gibi, " derleyicinin iblisleri burnunuzdan uçurması yasaldır. ". Tanımsız davranışınız olduğunda bir segfault'a güvenmeyin. C ve/veya C++ ' da hangi tanımsız davranışların bulunduğunu öğrenmeli ve bunları içeren kod yazmaktan kaçınmalısınız!

tanımsız davranış hakkında daha fazla bilgi:


bir Segfault nedir?

Kısacası, kod ' erişmek için izin yok bellek erişmeye çalıştığında bir bölümleme hatası neden olur . Her programa çalışmak için bir parça bellek (RAM) verilir ve güvenlik nedeniyle, yalnızca bu parçada belleğe erişmesine izin verilir.

bir segmentasyon hatası hakkında daha kapsamlı bir teknik açıklama için , bkz. bölümleme hatası nedir? .

burada bir segmentasyon hatası hatasının en yaygın nedenleri vardır. Yine, bunlar mevcut bir segfault tanısında kullanılmalıdır . Onlardan nasıl kaçınacağınızı öğrenmek için, dilinizin tanımsız davranışları öğrenin .

bu liste de kendi hata ayıklama iş yapmak için hiçbir yedek . (Cevabın altındaki bölüme bakın.) Bunlar arayabileceğiniz şeylerdir, ancak hata ayıklama araçlarınız sorun üzerinde sıfırlamanın tek güvenilir yoludur.


bir NULL veya başlatılmamış işaretçi erişme

NULL ( ptr=0 ) veya tamamen başlatılmamış bir işaretçiniz varsa (henüz hiçbir şeye ayarlanmamış), bu işaretçiyi kullanarak erişmeye veya değiştirmeye çalışıyorsanız tanımsızdır davranış.

int* ptr = 0;
*ptr += 5;

başarısız bir ayırma ( malloc veya new gibi ) null bir işaretçi döndürür, her zaman işaretçi ile çalışmadan önce NULL olmadığını kontrol etmelisiniz.

, okuma değerlerinin (dereferencing olmadan) başlatılmamış işaretçilerin (ve genel olarak değişkenler) tanımsız davranış olduğunu da unutmayın.

bazen tanımlanmamış bir erişim pointer, C print deyiminde böyle bir işaretçiyi bir dize olarak yorumlamaya çalışmak gibi oldukça ince olabilir.

char* ptr;
sprintf(id, "%s", ptr);

Ayrıca bakınız:


a erişme sarkan işaretçi

bellek ayırmak için malloc veya new kullanırsanız ve daha sonra free veya delete işaretçi aracılığıyla bellek, bu işaretçi şimdi sarkan işaretçi olarak kabul edilir. Dereferencing ıt (yanı sıra sadece okuma onun değeri - null gibi ona bazı yeni bir değer atamak vermedi verilen) tanımsız davranıştır, ve segmentasyon hatası neden olabilir.

Something* ptr = new Something(123, 456);
delete ptr;
std::cout << ptr->foo << std::endl;

Ayrıca bakınız:


yığın taşması

[hayır, şu anda olduğunuz site değil, adlı neydi.] Oversimplified, "yığın" böyle spike, sipariş kağıdınızı bazı yerlerde yapıştırın. Bu başak üzerinde çok fazla emir koyduğunuzda bu sorun oluşabilir, tabiri caizse. Bilgisayarda, olan herhangi bir değişken dinamik olarak tahsis edilmemiştir ve CPU tarafından işlenmemiş herhangi bir komut yığına gider.

bunun bir nedeni derin veya sonsuz özyineleme olabilir, örneğin bir işlev kendisini durdurmanın hiçbir yolu olmadan çağırdığında. Çünkü bu yığın taştı, sipariş. "düşmeye" başlayın ve onlar için tasarlanmamış başka bir yer kaplayın. Böylece, bir segmentasyon hatası alabiliriz. Başka bir neden, çok büyük bir diziyi başlatma girişimi olabilir:sadece tek bir emirdir, ancak zaten kendi başına yeterince büyüktür.

int stupidFunction(int n)
{
   return stupidFunction(n);
}

yığın taşmasının başka bir nedeni, aynı anda çok fazla (dinamik olarak tahsis edilmemiş) değişkene sahip olacaktır.

int stupidArray[600851475143];

vahşi bir yığın taşması bir olgu bir geldi basit ihmal bir return deyim bir işlevde sonsuz özyinelemeyi önlemek amacıyla bir koşullu. Bu hikayenin ahlaki, her zaman hata kontrolleri çalışmasını sağlamak!

Ayrıca bakınız:


vahşi işaretçiler

bellekte bazı rastgele konuma işaretçi oluşturma kodunuzla Rus ruleti oynamak gibi - kolayca kaçırmak ve erişim haklarına sahip olmayan bir konuma işaretçi oluşturabilirsiniz.

int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.

genel bir kural Olarak, gerçek bellek konumlarını işaretçiler yaratma. Bir kez çalışsalar bile, bir dahaki sefere çalışmayabilirler. Programınızın belleğinin nerede olduğunu tahmin edemezsiniz herhangi bir infaz olacak.

Ayrıca bakınız:


dizisinin sonunu okumaya çalışmak

bir dizi, ardışık her öğenin bellekte bir sonraki adreste bulunduğu bitişik bir bellek bölgesidir. Bununla birlikte, çoğu dizinin doğuştan gelen bir duygusu yoktur ne kadar büyük olduklarını veya son öğenin ne olduğunu. Böylece, dizinin sonuna kadar uçmak kolaydır ve özellikle işaretçi aritmetik kullanıyorsanız bunu asla bilemezsiniz.

dizinin sonunu okursanız, başlatılmamış veya başka bir şeye ait olan belleğe girebilirsiniz. Bu teknik olarak tanımsız davranış . Bir segfault sadece bu birçok potansiyel tanımsız davranışlardan biridir. [Açıkçası, burada bir segfault alırsanız, şanslısın. Diğerleri teşhis etmek daha zordur.]

// like most UB, this code is a total crapshoot.
int arr[3] {5, 151, 478};
int i = 0;
while(arr[i] != 16)
{
   std::cout << arr[i] << std::endl;
   i++;
}

veya for ile <= yerine < (1 bayt çok fazla okur)

kullanarak sık görülen
char arr[10];
for (int i = 0; i<=10; i++)
{
   std::cout << arr[i] << std::endl;
}

veya ince derleyen şanssız bir yazım hatası bile ( burada görülür) ve dim yerine dim öğeleriyle başlatılan yalnızca 1 öğeyi ayırır.

int* my_array = new int(dim);

buna ek olarak hatta (dereferencing söz değil) dizinin dışında işaret eden bir işaretçi oluşturmak için izin verilmediğini belirtmek gerekir (bu tür işaretçi yalnızca Dizideki bir öğeye işaret ediyorsa veya bir sonun geçmiş). Aksi takdirde, tanımsız davranışı tetikliyorsunuz.

Ayrıca bakınız:


unutmadan bir NUL bir C dizesinde sonlandırıcı.

C dizeleri, kendileri, bazı ek davranışları olan dizilerdir. Boş sonlandırılmış, sonunda bir '1519240920, güvenilir dizeleri olarak kullanılan olmalılar anlamı olmalı. Bu, bazı durumlarda ve diğerlerinde otomatik olarak yapılır.

bu unutulursa, C dizelerini ele alan bazı işlevler ne zaman duracağını asla bilemez ve bir dizinin sonundaki okuma ile aynı sorunları elde edebilirsiniz.

char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != ''151990920'')
{
   std::cout << str[i] << std::endl;
   i++;
}

C-dizeleri ile, '1519240920' herhangi bir fark yaratıp yaratmayacağını gerçekten özlüyor. Tanımsız davranışlardan kaçınacağını varsaymalısınız: char str[4] = {'f', 'o', 'o', ''1519260920 ''};

yazmanız daha iyi

bir dize değişmez değeri

değiştirmeye çalışıyor

bir char * için bir dize değişmez değeri atarsanız, değiştirilemez. Mesela...

char* foo = "Hello, world!"
foo[7] = 'W';

...tetikleyiciler tanımsız davranış ve bir segmentasyon hatası olası bir sonuçtur.

Ayrıca bakınız:


yanlış ayırma ve Deallocation yöntemleri

malloc ve free birlikte, new ve delete birlikte kullanmanız gerekir, ve new[] ve delete[] birlikte. Onları karıştırırsanız, segfaults ve diğer garip davranışları alabilirsiniz.

Ayrıca bakınız:


araç zincirindeki hatalar.

bir derleyicinin makine kodu arka uç bir hata segfaults bir yürütülebilir içine geçerli kodu dönüm oldukça yeteneğine sahiptir. Linker'deki bir hata kesinlikle bunu yapabilir.

özellikle kendi kodunuz tarafından çağrılan UB olmadığı için korkutucu.

dedi ki, her zaman sorunun aksi ispatlanana kadar olduğunu varsaymalısınız.


Diğer Nedenler

segmentasyon hatalarının olası nedenleri tanımsız davranışların sayısı kadar çoktur ve standart dokümantasyonun bile listelenmesi için çok fazla şey vardır.

kontrol etmek için birkaç daha az yaygın neden:


hata ayıklama

hata ayıklama araçları bir segfault nedenlerini teşhis enstrümantal. Programınızı hata ayıklama bayrağı ( -g ) ile derleyin ve sonra segfault'un nerede olacağını bulmak için hata ayıklayıcınızla çalıştırın.

son Derleyiciler, -fsanitize=address ile bina destekler; bu da genellikle 2X hakkında daha yavaş çalışan ancak adresi algılayabilen programda sonuçlanır hatalar daha doğru. Bununla birlikte, diğer hatalar (başlatılmamış bellekten okuma veya dosya tanımlayıcıları gibi bellek dışı kaynakları sızıntı gibi) bu yöntemle desteklenmez ve aynı anda birçok hata ayıklama aracı ve ASan kullanmak imkansızdır.

Bazı Bellek Hata Ayıklayıcıları

  • GDB | Mac, Linux
  • valgrind (memcheck) / Linux
  • Dr. Bellek / Windows

ayrıca tanımlanmamış davranışları tespit etmek için statik analiz araçlarını kullanmanız önerilir - ancak yine de, tanımsız davranışları bulmanıza yardımcı olacak bir araçtır ve tanımsız davranışların tüm oluşumlarını bulmayı garanti etmezler.

Eğer gerçekten şanssızsanız, bir hata ayıklayıcı kullanarak (veya daha nadiren, hata ayıklama bilgileri ile yeniden derlemek) programın kodunu ve belleğini etkileyebilir segfault'un artık gerçekleşmediği kadar, heisenbug olarak bilinen bir fenomen .

63
cevap CodeMouse92 2018-05-13 13:24:34
kaynak

Diğer sorular c c++ segmentation-fault