"İnt * nums = {5, 2, 1, 4}" bölümleme hatasına neden olur


81

segfault'a neden olurken

değil. Şimdi:

baskılar 5.

Buna dayanarak, dizi başlatma gösteriminin, {}, bu veriyi, soldaki değişkene körü körüne yüklediğini tahmin ettim. İnt [] olduğunda, dizi istenildiği gibi doldurulur. İnt * olduğunda, işaretçi 5 ile doldurulur ve işaretçinin saklandığı yerin ardından bellek yerleri 2, 1 ve 4 ile doldurulur. Bu yüzden nums [0], 5'i iptal etmeye çalışır ve bir segfault'a neden olur.

Yanılıyorsam lütfen düzeltin. Ve eğer haklıysam, lütfen ayrıntılandırın, çünkü dizi başlatıcılarının neden bu şekilde çalıştığını anlamıyorum.



3
Tüm uyarıları etkinleştirerek derleyin ve derleyiciniz size ne olduğunu söylemelidir.
Jabberwocky

1
@GSerg Bu, bir kopyaya yakın bir yerde değil. Bu soruda dizi işaretçisi yok. O gönderideki bazı cevaplar buradakilere benzese de.
Lundin

2
@Lundin% 30 emindim, bu yüzden kapatmak için oy vermedim, sadece bağlantıyı gönderdim.
GSerg

3
GCC'yi -pedantic-errorsbayrakla çalıştırma alışkanlığı edinin ve teşhisi izleyin. int *nums = {5, 2, 1, 4};geçerli değil C.
AnT

Yanıtlar:


113

C'de herhangi bir düz değişkenin ayraçlı bir başlatıcı listesiyle, tıpkı bir diziymiş gibi başlatılabileceğini söyleyen (aptalca) bir kural vardır.

Örneğin int x = {0};, tamamen eşdeğer olan yazabilirsiniz int x = 0;.

Yani yazarken int *nums = {5, 2, 1, 4};aslında tek bir işaretçi değişkenine bir başlatıcı listesi veriyorsunuz. Ancak, yalnızca tek bir değişkendir, bu nedenle yalnızca ilk 5 değeri atanır, listenin geri kalanı göz ardı edilir (aslında fazla başlatıcıya sahip kodun katı bir derleyici ile derlenmesi gerektiğini bile düşünmüyorum) - öyle değil hafızaya yazılsın. Kod eşdeğerdir int *nums = 5;. Bu nums, adresi göstermesi gerektiği anlamına gelir 5.

Bu noktada zaten iki derleyici uyarısı / hatası almış olmalısınız:

  • Atamasız işaretçiye tamsayı atama.
  • Başlatıcı listesindeki fazla öğeler.

Ve sonra elbette kod çökecek ve yanacaktır 5, çünkü büyük olasılıkla referansını kaldırmanıza izin verilen geçerli bir adres değildir nums[0].

Bir yan not olarak, printfadresleri %pbelirticiyle göstermelisiniz, aksi takdirde tanımsız davranışı çağırırsınız.


Burada ne yapmaya çalıştığınızdan tam olarak emin değilim, ancak bir göstericiyi bir diziyi gösterecek şekilde ayarlamak istiyorsanız, yapmanız gereken:

Veya bir dizi işaretçi oluşturmak istiyorsanız:


DÜZENLE

Biraz araştırmadan sonra "fazla eleman başlatıcı listesi" nin gerçekten geçerli C olmadığını söyleyebilirim - bu bir GCC uzantısıdır .

Standart 6.7.9 Başlatma diyor ki (vurgu benim):

2 Hiçbir başlatıcı, başlatılan varlık içinde yer almayan bir nesne için bir değer sağlamaya çalışmayacaktır.

/ - /

11 Bir skaler için başlatıcı, isteğe bağlı olarak parantez içine alınmış tek bir ifade olacaktır. Nesnenin başlangıç ​​değeri, ifadenin değeridir (dönüştürmeden sonra); Basit atamayla aynı tür kısıtlamaları ve dönüştürmeleri geçerlidir, skalerin türünü, bildirilen türünün nitelenmemiş sürümü olarak alır.

"Skaler tip", dizi, yapı veya birleşim tipi olmayan tek değişkenlere atıfta bulunan standart bir terimdir (bunlara "toplu tip" denir).

Yani düz İngilizce'de standart şöyle der: "Bir değişkeni başlattığınızda, sadece yapabildiğiniz için, başlatıcı ifadesinin etrafına fazladan parantezler atmakta özgürsünüz."


11
Skaler bir nesneyi içine alınmış tek bir değerle başlatma yeteneği hakkında "aptalca" bir şey yoktur {}. Aksine, { 0 }evrensel sıfır başlatıcı olarak C dilinin en önemli ve kullanışlı deyimlerinden birini kolaylaştırır . C'deki her şey sıfırdan başlatılabilir = { 0 }. Bu, türden bağımsız kod yazmak için çok önemlidir.
AnT

3
@AnT "Evrensel sıfır başlatıcı" diye bir şey yoktur. Toplamalar durumunda, {0}sadece ilk nesneyi sıfırlamak ve geri kalan nesneleri sanki statik depolama süresine sahipmiş gibi başlatmak anlamına gelir. Bunun, bazı "evrensel başlatıcıların" kasıtlı dil tasarımından ziyade tesadüfen olduğunu söyleyebilirim, çünkü {1}tüm nesneleri 1 olarak başlatmaz.
Lundin

3
@Lundin C11 6.5.16.1/1 kapakları p = 5;(listelenen durumların hiçbiri işaretçiye tamsayı atamak için karşılanmaz); 6.7.9 / 11, atama için kısıtlamaların başlatma için de kullanıldığını söylüyor.
MM

4
@Lundin: Evet, var. Hangi mekanizmanın nesnenin hangi bölümünü başlattığı tamamen ilgisizdir. Ayrıca, {}özellikle bu amaçla skalerlerin başlatılmasına izin verilip verilmeyeceği tamamen ilgisizdir . Önemli olan tek şey, başlatıcının tüm nesneyi sıfırlama= { 0 } garantisi vermesidir , bu da onu klasik ve C dilinin en zarif deyimlerinden biri yapan şeydir.
AnT

2
@Lundin: Konuyla ilgili yorumunuzun ne alakası olduğu da benim için tamamen belirsiz {1}. Hiç kimse , bunu agregatın her bir üyesi için çoklu başlatıcı olarak {0}yorumladığını iddia etmedi 0.
AnT

28

SENARYO 1

Bu neden segfault yapıyor?

numsİnt için bir gösterici olarak ilan ettiniz - bu, bellekte bir tamsayının numsadresini tutması gerekiyor .

Daha sonra birden çok değerden numsoluşan bir diziyi başlatmaya çalıştınız . Bu nedenle, çok fazla ayrıntıya girmeden, bu kavramsal olarak yanlıştır - tek bir değeri tutması gereken bir değişkene birden fazla değer atamak mantıklı değildir. Bu bağlamda, bunu yaparsanız tam olarak aynı etkiyi görürsünüz:

Her iki durumda da (bir işaretçiye veya bir int değişkenine birden çok değer atayın), o zaman değişkenin ilk değeri alması, 5kalan değerler göz ardı edilir. Bu kod uyumludur ancak atamada olmaması gereken her ek değer için uyarılar alırsınız:

warning: excess elements in scalar initializer.

İşaret değişkenine birden çok değer atanması durumunda, program eriştiğinizde hata verir nums[0], bu da adres 5'te depolanan her şeyi harfi harfine ertelediğiniz anlamına gelir . numsBu durumda işaretçi için geçerli bir bellek ayırmadınız .

İnt değişkenine birden çok değer atama durumunda herhangi bir segfault olmadığını belirtmekte fayda var (burada herhangi bir geçersiz göstericiye başvuruda bulunmuyorsunuz).


SENARYO 2

Bu, segfault değildir, çünkü yığında yasal olarak 4 inçlik bir dizi tahsis ediyorsunuz.


SENARYO 3

Bu, beklendiği gibi segfault vermez, çünkü siz işaretçinin değerini yazdırıyorsunuz - başvurunun iptalini DEĞİL (bu geçersiz bellek erişimidir).


Diğerleri

Bir işaretçinin değerini bu şekilde kodladığınızda neredeyse her zaman segfault'a mahkumdur (çünkü hangi işlemin hangi bellek konumuna erişebileceğini belirlemek işletim sisteminin görevidir).

Dolayısıyla, temel bir kural, her zaman bazı tahsis edilmiş değişkenlerin adresine bir gösterici başlatmaktır , örneğin:

veya,


2
+1 Bu iyi bir tavsiye, ancak birçok platformdaki sihirli adresler göz önüne alındığında "asla" gerçekten çok güçlü değildir. (Bu sabit adresler için sabit tablolar kullanmak mevcut değişkenleri göstermez ve bu nedenle de belirtildiği gibi kuralınızı ihlal eder.) Sürücü geliştirme gibi düşük seviyeli şeyler bu tür şeylerle oldukça sık ilgilenir.
Nate

3
"Bu geçerlidir" - fazla başlatıcıları göz ardı etmek bir GCC uzantısıdır; Standart C'de buna izin verilmiyor
MM

1
@TheNate - evet haklısın. Yorumunuzun temelini düzenledim - teşekkürler.
artm

@MM - bunu belirttiğiniz için teşekkürler. Bunu kaldırmak için düzenledim.
artm

25

int *nums = {5, 2, 1, 4};kötü biçimlendirilmiş koddur. Bu kodu şu şekilde değerlendiren bir GCC uzantısı var:

bellek adresi 5'e bir işaretçi oluşturmaya çalışmak (Bu bana kullanışlı bir uzantı gibi görünmüyor, ancak sanırım geliştirici tabanı bunu istiyor).

Bu davranıştan kaçınmak için (veya en azından bir uyarı alabilirsiniz), örneğin standart modda derleyebilirsiniz -std=c11 -pedantic.

Geçerli kodun alternatif bir biçimi şöyle olabilir:

ile aynı depolama süresine sahip değiştirilebilir bir değişmezi işaret eder nums. Ancak, int nums[]sürüm daha az depolama alanı kullandığından genellikle daha iyidir sizeofve dizinin ne kadar uzun olduğunu saptamak için kullanabilirsiniz .


Bileşik-değişmez biçimdeki dizinin, en az onun kadar uzun bir depolama ömrüne sahip olması garanti edilir numsmi?
supercat

@supercat evet, sayılar otomatikse otomatik, sayılar statikse statiktir
MM

@MM: Bu nums, bir işlev içinde statik bir değişken tanımlanmış olsa bile geçerli olur mu , yoksa derleyici, statik bir değişkene atanmış olsa bile dizinin ömrünü çevreleyen bloğun yaşam süresiyle sınırlama hakkına sahip olur mu?
supercat

@supercat evet (ilk bit). İkinci seçenek, işlev ikinci kez çağrıldığında UB anlamına gelir (çünkü statik değişkenler yalnızca ilk çağrıda başlatılır)
MM

12

numsbir tür göstericidir int. Bu nedenle, bunu geçerli bir bellek konumuna yönlendirmelisiniz. num[0]bazı rasgele bellek konumlarından ve dolayısıyla bölümleme hatasından vazgeçmeye çalışıyorsunuz.

Evet, işaretçi 5 değerini tutuyor ve siz bunun sisteminizde tanımsız bir davranış olan referansını kaldırmaya çalışıyorsunuz. (Görünüşe göre 5sisteminizde geçerli bir bellek konumu değil)

Buna karşılık

bir numstür dizisi olduğunu söylediğiniz intve belleğin, başlatma sırasında geçen öğelerin sayısına göre tahsis edildiği geçerli bir bildirimdir .


1
"Evet, işaretçi 5 değerini tutuyor ve siz bunun tanımsız bir davranış olan referansını kaldırmaya çalışıyorsunuz." Hiç de değil, tamamen iyi ve iyi tanımlanmış bir davranış. Ancak OP'nin kullandığı sistemde bu geçerli bir hafıza adresi değil, dolayısıyla çökme.
Lundin

@Lundin Kabul ediyorum. Ama sanırım OP 5'in geçerli bir hafıza konumu olduğunu asla bilmedi, bu yüzden bu satırlar üzerinden konuştum. Düzenleme yardımcı olur umarım
Gopi

Böyle mi olmalı? int *nums = (int[]){5, 2, 1, 4};
Islam Azab

10

Atayarak {5, 2, 1, 4}

5'e numsatıyorsunuz (int'ten int'e işaretçiye örtük bir typecast'ten sonra). Aktarıldıktan sonra adresindeki hafıza konumuna bir erişim çağrısı yapar 0x5. Programınızın erişimine izin verilmeyebilir.

Deneyin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.