Başlatılmamış üyelerle yapıları kopyalama


29

Bazı üyeleri başlatılmamış bir yapıyı kopyalamak geçerli mi?

Tanımlanmamış bir davranış olduğundan şüpheleniyorum, ancak eğer öyleyse, başlatılmamış üyeleri bir yapıda (bu üyeler asla doğrudan kullanılmasa bile) bırakmayı oldukça tehlikeli hale getirir. Standartta buna izin veren bir şey olup olmadığını merak ediyorum.

Örneğin, bu geçerli mi?

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

Bir süre önce benzer bir soru gördüğümü hatırlıyorum ama bulamıyorum. Bu soru olarak ilişkilidir bu bir .
1201ProgramAlarm

Yanıtlar:


23

Evet, başlatılmamış üye imzasız bir dar karakter türü değilse veya std::bytebu belirsiz değeri içeren bir yapıyı örtük olarak tanımlanmış kopya yapıcısıyla kopyalamak aynı tür belirsiz değeri olan bir değişkeni kopyalamak için olduğu gibi teknik olarak tanımlanmamış bir davranıştır. / dcl.init] / 12 .

Örtük olarak oluşturulan kopya yapıcısı, unions hariç , her üyeyi doğrudan başlatma ile sanki ayrı ayrı kopyalayacak şekilde tanımlandığı için burada geçerlidir . [Class.copy.ctor] / 4 .

Bu aynı zamanda aktif CWG sayısı 2264'e de tabidir .

Sanırım pratikte bununla ilgili bir sorununuz olmayacak.

% 100 emin olmak istiyorsanız , üyelerin belirsiz değeri olsa bile std::memcpy, türün önemsiz bir şekilde kopyalanması durumunda kullanmak her zaman iyi tanımlanmış bir davranışa sahiptir.


Bu sorunlar bir yana, sınıfın önemsiz bir varsayılan kurucuya sahip olmasını gerektirmediğinizi varsayarak, sınıf üyelerinizi her zaman inşaatta belirtilen bir değerle düzgün bir şekilde başlatmanız gerekir . Varsayılan üye başlatıcı sözdizimini kullanarak üyeleri kolayca başlatabilirsiniz;

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

Peki .. Bu yapı bir POD (Düz eski veriler) değil mi? Bu, üyelerin varsayılan değerlerle başlatılacağı anlamına mı geliyor? Bu bir şüphe
Kevin Kouketsu

Bu durumda sığ kopya değil mi? kopyalanmamış yapıda başlatılmamış üyeye erişilmedikçe bununla ilgili sorun ne olabilir?
TruthSeeker

@KevinKouketsu Önemsiz / POD tipinin gerekli olduğu bir durum için bir koşul ekledim.
ceviz

@TruthSeeker Standart, tanımsız davranış olduğunu söylüyor. (Üye olmayan) değişkenler için genellikle tanımlanmamış davranış olmasının nedeni AndreySemashev tarafından verilen cevapta açıklanmıştır. Temel olarak, başlatılmamış belleğe sahip tuzak temsillerini desteklemektir. Bunun yapıların örtük kopya yapımına uygulanması amaçlanıp kaynaklanmadığı bağlantılı CWG sorununun konusudur.
ceviz

@TruthSeeker Örtük kopya oluşturucu, her üyeyi doğrudan başlatma ile sanki tek tek kopyalayacak şekilde tanımlanır. memcpyÖnemsiz olarak kopyalanabilen türler için bile nesne temsilini kopyalamak gibi tanımlanmamıştır . Tek istisna, örtük kopya yapıcısının nesne gösterimini sanki by gibi kopyaladığı sendikalardır memcpy.
ceviz

11

Genel olarak, başlatılmamış verilerin kopyalanması tanımlanmamış bir davranıştır, çünkü bu veriler bir bindirme durumunda olabilir. Bu sayfadan alıntı yapılıyor :

Bir nesne temsili nesne türünün herhangi bir değerini temsil etmiyorsa, bu, tuzak temsili olarak bilinir. Bir tuzak temsiline, karakter türünün lvalue ifadesiyle okumak dışında herhangi bir şekilde erişmek, tanımsız bir davranıştır.

Kayan nokta tipleri için sinyal NaN'leri mümkündür ve bazı platformlarda tamsayılarda tuzak temsili olabilir .

Ancak, önemsizmemcpy biçimde kopyalanabilen tipler için, nesnenin ham temsilini kopyalamak mümkündür . Nesnenin değeri yorumlanmadığı ve bunun yerine nesne gösteriminin ham bayt dizisi kopyalandığı için bunu yapmak güvenlidir.


Tüm bit modellerinin geçerli değerleri temsil ettiği türdeki veriler hakkında (örneğin, an içeren 64 baytlık bir yapı unsigned char[64])? Bir yapının baytlarına Belirtilmemiş değerlere sahip olarak davranmak, optimizasyonu gereksiz yere engelleyebilir, ancak programcıların diziyi yararsız değerlerle manuel olarak doldurmalarını istemek verimliliği daha da engelleyecektir.
supercat

Verilerin başlatılması işe yaramaz, ister tuzak gösterimlerinden, ister daha sonra başlatılmamış verilerin kullanılmasından kaynaklanıp kaynaklanmadığını UB'yi önler. 64 baytı sıfırlamak (1 veya 2 önbellek satırı) göründüğü kadar pahalı değildir. Pahalı olduğu büyük yapılarınız varsa, kopyalamadan önce iki kez düşünmelisiniz. Ve eminim ki bir noktada onları başlatmanız gerekecek.
Andrey Semashev

Bir programın davranışını etkileyemeyen makine kodu işlemleri işe yaramaz. Standart tarafından UB olarak nitelendirilen herhangi bir eylemin her ne pahasına olursa olsun kaçınılması gerektiği fikrinden kaçınılmalıdır; bunun yerine, [C Standartlar Komitesi'nin sözleriyle] UB'nin “uygun dil genişletme alanlarını belirlediği” söylenebilir. C ++ Standardı için yayınlanan bir Gerekçe görmemiş olsam da, C ++ programlarının programları uygun veya uygun olmayan olarak kategorize etmeyi reddederek hangi C ++ programlarının yapmasına izin verildiğine ilişkin yargı yetkisinden açıkça feragat eder, yani benzer uzantılara izin verir.
supercat

-1

Anlatılan gibi bazı durumlarda, C ++ Standardı, derleyicilerin, davranışlarının öngörülebilir olmasını gerektirmeden, müşterilerini en yararlı bulacakları şekilde yapıları işlemesine izin verir. Başka bir deyişle, bu tür yapılar "Tanımsız Davranış" ı çağrıştırır. Bununla birlikte, bu tür yapıların "yasak" anlamına geldiği anlamına gelmez, çünkü C ++ Standardı, iyi biçimlendirilmiş programların ne yapmasına izin verildiğine ilişkin yargı yetkisini açıkça feragat eder. C ++ Standardı için yayınlanan herhangi bir Rationale belgesinin farkında olmamakla birlikte, C89 gibi Tanımsız Davranışı tanımlaması, amaçlanan anlamın benzer olduğunu düşündürür: "Tanımsız davranış, uygulayıcıya zor olan bazı program hatalarını yakalamamasını sağlar teşhis etmek.

Bir şeyi işlemenin en etkili yolunun, bir yapının aşağı akış kodunun ilgileneceği kısımlarını yazmayı içerdiği ve aşağı akış kodunun ilgilenmeyeceği şeyleri atladığı birçok durum vardır. Programların, hiçbir şeyin umursamayacağı olanlar da dahil olmak üzere, bir yapının tüm üyelerini başlatmasını zorunlu kılmak, verimliliği gereksiz yere engelleyecektir.

Ayrıca, başlatılmamış verilerin deterministik olmayan bir şekilde davranmasının en etkili olabileceği bazı durumlar vardır. Örneğin, verilenler:

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

alt kodu herhangi elemanlarının değerleri bakım eğer x.datya y.datolan endeksler listelenmeyen edildi arr, kod için optimize edilebilir:

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

Programcıların temp.dat, kopyalamadan önce aşağı yönde ilgilenmeyenler de dahil olmak üzere, her öğesini açıkça yazmaları gerekiyorsa, verimlilikteki bu gelişme mümkün olmazdı.

Öte yandan, veri sızıntısı olasılığından kaçınmanın önemli olduğu bazı uygulamalar vardır. Bu tür uygulamalarda, aşağı akış kodunun ona bakıp bakmayacağına bakılmaksızın başlatılmamış depolama alanını kopyalamaya yönelik herhangi bir girişimi yakalamak için kullanılan kodun bir sürümüne sahip olmak yararlı olabilir veya herhangi bir depolama alanının İçeriği sızdırabilenler, gizli olmayan verilerle sıfırlanır veya üzerine yazılır.

Söyleyebileceğim kadarıyla, C ++ Standardı, bu davranışların herhangi birinin diğerini zorunlu kılmak için yeterince yararlı olduğunu söylemek için hiçbir girişimde bulunmaz. İronik olarak, bu spesifikasyon eksikliğinin optimizasyonu kolaylaştırması amaçlanabilir, ancak programcılar herhangi bir zayıf davranışsal garantiden yararlanamazsa, herhangi bir optimizasyon reddedilecektir.


-2

Tüm üyeleri Datailkel tür olduğundan, tüm üyelerin data2tam "bit-bit kopya" alacak data. Yani değeri data2.btam olarak değeri ile aynı olacaktır data.b. Bununla birlikte, data.baçık bir şekilde başlatmadığınız için , kesin değeri tahmin edilemez. İçin ayrılan bellek bölgesindeki bayt değerlerine bağlı olacaktır data.


Bunu standarda referansla destekleyebilir misiniz? @Walnut tarafından sağlanan bağlantılar, bunun tanımsız bir davranış olduğunu ima eder. Standarttaki POD'lar için bir istisna var mı?
Tomek Czajka

Aşağıdakiler standarda bağlı olmasa da, hala: en.cppreference.com/w/cpp/language/… "Önemsiz Kopyalanabilir nesneler, nesne gösterimlerini örneğin std :: memmove ile manuel olarak kopyalayarak kopyalanabilir. C ile uyumlu tüm veri türleri dil (POD türleri) önemsiz bir şekilde kopyalanabilir. "
ivan.ukr

Bu durumda tek "tanımsız davranış", başlatılmamış üye değişkenin değerini tahmin edemememizdir. Ancak kod başarıyla derlenir ve çalışır.
ivan.ukr

1
Alıntı yaptığınız parça memmove davranışı hakkında konuşuyor, ama gerçekten burada ilgili değil çünkü kodumda memmove değil, kopya yapıcı kullanıyorum. Diğer yanıtlar, kopya yapıcısının kullanılmasının tanımsız davranışla sonuçlandığını ima eder. Ayrıca "tanımsız davranış" terimini de yanlış anladığınızı düşünüyorum. Bu, dilin hiçbir garanti vermediği anlamına gelir; örneğin, program verileri rastgele bozabilir veya bozabilir veya herhangi bir şey yapabilir. Bu sadece bir değerin tahmin edilemez olduğu anlamına gelmez, bu belirtilmemiş davranışlar olacaktır.
Tomek Czajka

@ ivan.ukr C ++ standardı, örtük kopyala / taşı yapıcılarının üye olarak doğrudan başlatma gibi davranarak yanıtımdaki bağlantılara baktığını belirtir. Bu nedenle kopya inşaat yok değil , bir "yapmak 'bit-bit kopyasını' ". Sen örtük kopya yapıcı hangi sendika türleri için sadece doğru olduğu gibi manuel tarafından nesne temsilini kopyalamak için belirtilen std::memcpy. Bunların hiçbiri std::memcpyveya kullanımını engellemez std::memmove. Yalnızca örtük kopya yapıcısının kullanılmasını önler.
ceviz
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.