Neden C ve C ++ yapılar içindeki dizilerin üyeler tarafından atanmasını destekler, ancak genel olarak desteklemez?


87

Üyelerin dizi atamasının desteklenmediğini anlıyorum, bu nedenle aşağıdakiler çalışmayacaktır:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

Dilin amacının açık uçlu bir çerçeve sağlamak olduğunu düşünerek bunu gerçek olarak kabul ettim ve kullanıcının bir dizinin kopyalanması gibi bir şeyi nasıl uygulayacağına karar vermesine izin verdim.

Ancak, aşağıdakiler işe yarar:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

Dizi num[3]üye akıllı içinde örneğinden atanmış olan struct1kendi örneğine, struct2.

Dizilerin üye bazında atanması neden yapılar için destekleniyor, ancak genel olarak desteklenmiyor?

edit : Roger Pate'in thread içindeki yorumu std :: string in struct - Kopyalama / atama sorunları? cevabın genel yönünü gösteriyor gibi görünüyor, ancak bunu kendim doğrulayacak kadar bilgim yok.

düzenleme 2 : Birçok mükemmel yanıt. Ben seçim Luther Blissettde 'Ben çoğunlukla davranışın ardındaki felsefi ya da tarihsel mantığı merak ediyordum çünkü s, ancak James McNellis ' ilgili Spec belgelerine ler referans yanı faydalı oldu.


6
Bunun etiket olarak hem C hem de C ++ olmasını sağlıyorum, çünkü bu C'den geliyor. Ayrıca, iyi soru.
GManNickG

4
Uzun zaman önce C'de yapı atamasının genellikle mümkün olmadığını ve kullanmak memcpy()veya benzerlerini kullanmak zorunda olduğunuzu belirtmekte fayda var .
ggg

Bilginize biraz ... boost::array( boost.org/doc/libs/release/doc/html/array.html ) ve şimdi std::array( en.cppreference.com/w/cpp/container/array ) STL uyumlu alternatiflerdir. dağınık eski C dizileri. Kopyalama atamayı desteklerler.
Emile Cormier

@EmileCormier Ve onlar - tada! - diziler etrafındaki yapılar.
Peter - Monica'yı yeniden

Yanıtlar:


46

İşte benim görüşüm:

C Dilinin Gelişimi, C'deki dizi türünün evrimi hakkında bazı bilgiler sunar:

Dizi olayını özetlemeye çalışacağım:

C'nin öncüleri B ve BCPL'nin farklı bir dizi türü yoktu, şöyle bir bildirim:

auto V[10] (B)
or 
let V = vec 10 (BCPL)

V'nin kullanılmayan 10 "word" bellek bölgesine işaret edecek şekilde başlatılan (tiplenmemiş) bir işaretçi olduğunu ilan eder. B zaten kullanılmış *işaretçiyi kaldırma ve vardı [] , kısa eli notasyonu *(V+i)demek V[i]sadece C / C ++ bugün olduğu gibi. Bununla birlikte, Vbir dizi değil, yine de bir belleğe işaret etmesi gereken bir göstericidir. Bu, Dennis Ritchie B'yi yapı türleriyle genişletmeye çalıştığında soruna neden oldu. Dizilerin bugünkü C'de olduğu gibi yapıların bir parçası olmasını istedi:

struct {
    int inumber;
    char name[14];
};

Ancak işaretçiler olarak B, BCPL diziler kavramı ile, bu, namealanın , çalışma zamanında yapı içinde 14 baytlık bir bellek bölgesine başlatılması gereken bir işaretçi içermesini gerektirecekti . Başlatma / düzen problemi sonunda dizilere özel bir muamele verilerek çözüldü: Derleyici, dizileri içeren ifadeler haricinde, verinin gerçeklenmesi için göstericiye gerçekten ihtiyaç duymadan yapılardaki, yığın üzerindeki vb. Dizilerin konumunu izlerdi. Bu işlem, neredeyse tüm B kodunun hala çalışmasına izin verdi ve "dizilere bakarsanız işaretçiye dönüştürün" kuralının kaynağıdır . Açık boyuttaki dizilere izin verdiği için çok kullanışlı olduğu ortaya çıkan bir uyumluluk saldırısıdır.

Ve tahminim neden dizi atanamaz: Diziler B'de işaretçiler olduğundan, basitçe yazabilirsiniz:

auto V[10];
V=V+5;

bir "diziyi" yeniden oluşturmak için. Bu artık anlamsızdı, çünkü bir dizi değişkeninin tabanı artık bir değer değildi. Dolayısıyla, bu atamaya izin verilmedi ve bu, bu yeniden düzenlemeyi yapan birkaç programın yakalanmasına yardımcı oldu da bildirilen dizilerde. Ve sonra bu fikir sıkıştı: Diziler hiçbir zaman C tipi sistemden birinci sınıf alıntılanacak şekilde tasarlanmadıkları için, çoğunlukla, onları kullanırsanız işaretçi haline gelen özel canavarlar olarak ele alındı. Ve belirli bir bakış açısına göre (bu, C-dizilerinin başarısız bir hack olduğunu göz ardı eder), dizi atamasına izin vermemek yine de bir anlam ifade eder: Açık bir dizi veya bir dizi işlevi parametresi, boyut bilgisi olmayan bir işaretçi olarak değerlendirilir. Derleyici, onlar için bir dizi ataması oluşturacak bilgiye sahip değildir ve uyumluluk nedeniyle işaretçi ataması gerekliydi.

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

Bu, 1978'de bir C revizyonu yapı ataması eklediğinde değişmedi ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Kayıtlar olsa idi C farklı türünün, bu kopyalamak zorunda erken K & R C You onları atamak mümkün değildi onlara üye akıllı memcpy ile ve fonksiyon parametreleri olarak onlara sadece işaretçileri geçebileceği. Atama (ve parametre geçişi) artık basitçe yapının ham belleğinin memcpy'si olarak tanımlanıyordu ve bu mevcut kodu kıramadığı için kolayca geliştirildi. Kasıtsız bir yan etki olarak, bu örtük olarak bir tür dizi atamasını başlattı, ancak bu bir yapının içinde bir yerde gerçekleşti, bu nedenle bu, dizilerin kullanılma biçimiyle gerçekten sorun yaratamaz.


C'nin bir sözdizimi tanımlamaması çok kötü, örneğin int[10] c;lvalue'nun con maddelik bir dizinin ilk maddesine bir gösterici olarak değil, on maddelik bir dizi gibi davranmasını sağlamak için . Bir değişken için kullanıldığında alan ayıran, ancak bir işlev argümanı olarak kullanıldığında bir işaretçi ileten bir typedef oluşturmanın yararlı olduğu birkaç durum vardır, ancak dizi türünün bir değerine sahip olamama önemli bir anlamsal zayıflıktır. dilde.
supercat

"Bir hafızaya işaret etmesi gereken işaretçi" demek yerine, önemli olan nokta işaretçinin kendisinin normal bir işaretçi gibi hafızada saklanması gerektiğidir . Bu, sonraki açıklamanızda karşımıza çıkıyor, ancak bence bu, temel farkı daha iyi vurguluyor. (Modern C'de, bir dizi değişkeninin adı bir bellek bloğuna atıfta bulunur, bu yüzden fark bu değildir. İşaretçinin kendisinin mantıksal olarak soyut makinede herhangi bir yerde depolanmamasıdır.)
Peter Cordes

Geçmişin güzel bir özeti için C'nin dizilerden hoşlanmamasına bakın .
Peter Cordes

31

Atama operatörleri ile ilgili olarak, C ++ standardı şunları söyler (C ++ 03 §5.17 / 1):

Birkaç atama operatörü vardır ... hepsi sol işlenenleri olarak değiştirilebilir bir ldeğer gerektirir

Bir dizi değiştirilebilir bir değer değildir.

Ancak, bir sınıf türü nesneye atama özel olarak tanımlanır (§5.17 / 4):

Bir sınıfın nesnelerine atama, kopya atama operatörü tarafından tanımlanır.

Bu nedenle, bir sınıf için örtük olarak bildirilmiş kopya atama operatörünün ne yaptığını görmeye bakarız (§12.8 / 13):

X sınıfı için örtük olarak tanımlanmış kopya atama operatörü, alt nesnelerinin üyelere göre atamasını gerçekleştirir. ... Her alt nesne türüne uygun şekilde atanır:
...
- alt nesne bir diziyse, her öğe, öğe türüne uygun şekilde atanır
...

Dolayısıyla, sınıf türü bir nesne için diziler doğru şekilde kopyalanır. Kullanıcı tarafından beyan edilen bir kopya atama operatörü sağlarsanız, bundan yararlanamayacağınızı ve diziyi öğe öğe kopyalamanız gerekeceğini unutmayın.


Gerekçe C'de benzerdir (C99 §6.5.16 / 2):

Bir atama operatörü, sol işlenen olarak değiştirilebilir bir değere sahip olacaktır.

Ve §6.3.2.1 / 1:

Değiştirilebilir bir değer, dizi türüne sahip olmayan bir değerdir ... [diğer kısıtlamalar takip eder]

C'de atama, C ++ 'dan çok daha basittir (§6.5.16.1 / 2):

Basit atamada (=), sağ işlenenin değeri atama ifadesinin türüne dönüştürülür ve sol işlenen tarafından belirlenen nesnede depolanan değerin yerini alır.

Yapı tipi nesnelerin atanması için, sol ve sağ işlenenlerin aynı türe sahip olması gerekir, bu nedenle sağ işlenenin değeri basitçe soldaki işlenene kopyalanır.


1
Diziler neden değişmez? Daha doğrusu, sınıf türünde olduğu gibi neden diziler için özel olarak tanımlanmıyor?
GManNickG

1
@GMan: Bu daha ilginç bir soru, değil mi? C ++ için cevap muhtemelen "C'de böyle olduğu için" olacaktır ve C için bunun sadece dilin nasıl geliştiğine bağlı olduğunu tahmin ediyorum (yani, neden tarihseldir, teknik değil), ama hayatta değildim bunların çoğu gerçekleştiğinde, bu bölümü yanıtlamayı daha bilgili birine bırakacağım: -P (FWIW, C90 veya C99 mantık belgelerinde hiçbir şey bulamıyorum).
James McNellis

2
C ++ 03 standardında "değiştirilebilir değer" tanımının nerede olduğunu bilen var mı? Bu olmalıdır §3.10 olmak. Dizin o sayfada tanımlandığını söylüyor, ama değil. §8.3.4 / 5'teki (normatif olmayan) not "Dizi türlerinin nesneleri değiştirilemez, bkz. 3.10" diyor, ancak §3.10 bir kez "dizi" kelimesini kullanmıyor.
James McNellis

@James: Ben de aynısını yapıyordum. Kaldırılmış bir tanıma atıfta bulunuyor gibi görünüyor. Ve evet, hepsinin arkasındaki gerçek nedeni bilmek istemişimdir, ama bu bir gizem gibi görünüyor. "Yanlışlıkla diziler atayarak insanların verimsiz olmasını engelleyin" gibi şeyler duydum, ama bu çok saçma.
GManNickG

1
@GMan, James: Yakın zamanda comp.lang.c ++ groups.google.com/group/comp.lang.c++/browse_frm/thread/… üzerinde bir tartışma oldu, eğer kaçırdıysanız ve hala ilgileniyorsanız. Bir dizi değil değiştirilebilir bir lvalue çünkü Anlaşılan o değil (bir dizi kesinlikle lvalue ve const olmayan tüm Sol taraf değiştirilebilir vardır), ama çünkü =bir gerektirir rvalue üzerinde RHS ve bir dizi bir olamaz rvalue ! Lvalue-to-rvalue dönüşümü diziler için yasaklanmıştır ve lvalue-to-pointer ile değiştirilmiştir. static_castaynı terimlerle tanımlandığı için bir değer oluşturmada daha iyi değildir.
Potatoswatter

2

Bu bağlantıda: http://www2.research.att.com/~bs/bs_faq2.html dizi atamasıyla ilgili bir bölüm var:

Dizilerle ilgili iki temel sorun şudur:

  • bir dizi kendi boyutunu bilmiyor
  • bir dizinin adı, en ufak bir provokasyonda ilk elemanına bir göstericiye dönüşür

Ve diziler ve yapılar arasındaki temel farkın bu olduğunu düşünüyorum. Bir dizi değişkeni, sınırlı kişisel bilgiye sahip düşük seviyeli bir veri öğesidir. Temelde, bir yığın bellek ve onu indekslemenin bir yolu.

Dolayısıyla, derleyici int a [10] ve int b [20] arasındaki farkı anlayamaz.

Bununla birlikte, yapılar aynı belirsizliğe sahip değildir.


3
Bu sayfa dizilerin fonksiyonlara geçirilmesinden bahsediyor (ki bu yapılamaz, bu yüzden bu sadece bir gösterici, boyutunu kaybettiğini söylediğinde bunu kastediyor). Bunun dizilere diziler atamakla ilgisi yoktur. Ve hayır, bir dizi değişkeni sadece "gerçekten" ilk öğeye bir gösterici değildir, bir dizidir. Diziler işaretçi değildir.
GManNickG

Yorumunuz için teşekkürler, ancak makalenin o bölümünü okuduğumda, dizilerin kendi boyutunu bilmediğini söylüyor, ardından bu gerçeği açıklamak için dizilerin argümanlar olarak aktarıldığı bir örnek kullanıyor. Öyleyse, diziler bağımsız değişken olarak geçirildiğinde, büyüklükleriyle ilgili bilgileri mi kaybettiler ya da başlangıç ​​için hiçbir bilgiye sahip olmadılar. Ben ikincisini varsaydım.
Scott TurIey

3
Derleyici iki farklı büyüklükte diziler arasındaki farkı söyleyebilir - yazdırmayı deneyin sizeof(a)VS. sizeof(b)veya geçen aetmek void f(int (&)[20]);.
Georg Fritzsche

Her dizi boyutunun kendi türünü oluşturduğunu anlamak önemlidir. Parametre geçirme kuralları, boyutu ayrı ayrı iletme ihtiyacı pahasına, her boyutta dizi argümanı alan kötü adamın "genel" işlevlerini yazabilmenizi sağlar. Durum böyle değilse (ve C ++ 'da, belirli boyuttaki dizilere referans parametreleri tanımlayabilirsiniz ve tanımlamalısınız!), Her farklı boyut için belirli bir işleve ihtiyacınız vardır, açıkça saçma. Bunun hakkında başka bir yazıda yazdım .
Peter - Monica'yı yeniden

0

Biliyorum, cevaplayan herkes C / C ++ konusunda uzmandır. Ama birincil nedenin bu olduğunu düşündüm.

num2 = num1;

Burada dizinin izin verilmeyen temel adresini değiştirmeye çalışıyorsunuz.

ve tabii ki struct2 = struct1;

Burada, struct1 nesnesi başka bir nesneye atanır.


Ve yapıların atanması sonunda aynı soruyu soran dizi üyesini atayacaktır. Her iki durumda da bir dizi varken neden birine izin veriliyor, diğeri değil?
GManNickG

1
Kabul. Ancak birincisi derleyici tarafından engellenir (num2 = num1). İkincisi, derleyici tarafından engellenmez. Bu büyük bir fark yaratır.
nsivakr

Diziler atanabilir num2 = num1olsaydı, çok iyi davranırdı. Öğelerinin num2karşılık gelen öğesinin değeri aynı olur num1.
juanchopanza

0

C'deki dizileri güçlendirmek için daha fazla çaba gösterilmemesinin bir başka nedeni de, muhtemelen dizi atamasının böyle olmayacağıdır . yararlı . C'de onu bir yapıya sararak kolayca elde edilebilmesine rağmen (ve yapının adresi basitçe dizinin adresine veya hatta daha fazla işlem için dizinin ilk elemanının adresine dönüştürülebilir) bu özellik nadiren kullanılır. Bunun bir nedeni, farklı boyutlardaki dizilerin uyumsuz olmasıdır, bu da atamanın faydalarını veya ilişkili olarak işlevlere değere göre geçişi sınırlar.

Dizilerin birinci sınıf türler olduğu dillerde dizi parametrelerine sahip çoğu işlev, rastgele boyuttaki diziler için yazılır. Daha sonra işlev, dizinin sağladığı bir bilgi olan belirli sayıda öğe üzerinde yineleme yapar. (C'de deyim, elbette, bir işaretçi ve ayrı bir eleman sayımı geçirmektir.) Yalnızca belirli bir boyuttaki bir diziyi kabul eden bir işlev, çoğu kez gerekli değildir, bu nedenle çok fazla şey kaçırılmaz. (Bu, C ++ şablonlarında olduğu gibi ortaya çıkan herhangi bir dizi boyutu için ayrı bir işlev oluşturmak üzere onu derleyiciye bıraktığınızda değişir; std::arrayyararlı olmasının nedeni budur .)

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.