Aracılığıyla paylaş


Genel Visual C++ ARM Geçiş Sorunları

Microsoft C++ derleyicisini (MSVC) kullanırken, aynı C++ kaynak kodu ARM mimarisinde x86 veya x64 mimarilerindekinden farklı sonuçlar üretebilir.

Geçiş sorunlarının kaynakları

Kodu x86 veya x64 mimarilerinden ARM mimarisine geçirirken karşılaşabileceğiniz birçok sorun tanımsız, uygulama tanımlı veya belirtilmeyen davranışı çağırabilecek kaynak kodu yapılarıyla ilgilidir.

Tanımsız davranış , C++ standardının tanımlamadığı davranıştır ve bunun nedeni makul sonucu olmayan bir işlemdir: örneğin, kayan nokta değerini işaretsiz bir tamsayıya dönüştürme veya değeri negatif olan veya yükseltilen türündeki bit sayısını aşan bir dizi konumla kaydırma.

Uygulama tanımlı davranış , C++ standardının derleyici satıcısının tanımlamasını ve belgelesini gerektirmesi davranışıdır. Bir program, uygulama tanımlı davranışı güvenli bir şekilde kullanabilir, ancak bunu yapmak taşınabilir olmayabilir. Uygulama tanımlı davranışlara örnek olarak yerleşik veri türlerinin boyutları ve bunların hizalama gereksinimleri verilebilir. Uygulama tanımlı davranıştan etkilenebilen bir işlem örneği, değişken bağımsız değişkenleri listesine erişmektir.

Belirtilmeyen davranış , C++ standardının kasıtlı olarak belirleyici olmayan şekilde bırakması davranışıdır. Davranış belirlenimci olmayan olarak kabul edilse de, belirtilmeyen davranışın belirli çağrıları derleyici uygulaması tarafından belirlenir. Ancak, derleyici satıcısının sonucu önceden belirtmesi veya benzer çağrılar arasında tutarlı davranış garantisi verme gereksinimi yoktur ve belgelere gerek yoktur. Belirtilmeyen davranışlara örnek olarak işlev çağrısı bağımsız değişkenlerini içeren alt ifadelerin değerlendirilme sırası verilebilir.

Diğer geçiş sorunları, C++ standardıyla farklı şekilde etkileşim kuran ARM ve x86 veya x64 mimarileri arasındaki donanım farklılıklarına bağlanabilir. Örneğin, x86 ve x64 mimarisinin güçlü bellek modeli, nitelikli değişkenlere geçmişte belirli iş parçacıkları arası iletişimi kolaylaştırmak için kullanılan bazı ek özellikler sağlar volatile. Ancak ARM mimarisinin zayıf bellek modeli bu kullanımı desteklemez ve C++ standardı bunu gerektirmez.

Önemli

x86 ve x64'te sınırlı iş parçacıkları arası iletişim biçimleri uygulamak için kullanılabilecek bazı özellikler kazansa volatile da, bu ek özellikler genel olarak iş parçacıkları arası iletişimi uygulamak için yeterli değildir. C++ standardı, bu tür iletişimin bunun yerine uygun eşitleme temel bilgileri kullanılarak uygulanmasını önerir.

Farklı platformlar bu tür davranışları farklı ifade ettiğinden, belirli bir platformun davranışına bağlıysa platformlar arasında yazılım taşıma zor ve hataya açık olabilir. Bu tür davranışların çoğu gözlemlense ve kararlı görünse de, bunlara güvenmek en azından taşınabilir değildir ve tanımsız veya belirtilmeyen davranışlarda da bir hatadır. Bu belgede belirtilen davranışlara bile dayanılmamalıdır ve gelecekteki derleyicilerde veya CPU uygulamalarında değişebilir.

Örnek geçiş sorunları

Bu belgenin geri kalanında, bu C++ dil öğelerinin farklı davranışlarının farklı platformlarda nasıl farklı sonuçlar oluşturabileceği açıklanmaktadır.

Kayan noktanın işaretsiz tamsayıya dönüştürülmesi

ARM mimarisinde, kayan nokta değerinin 32 bitlik bir tamsayıya dönüştürülmesi, kayan nokta değeri tamsayının gösterebileceği aralığın dışındaysa tamsayının gösterebileceği en yakın değere doygunluğa ulaşır. x86 ve x64 mimarilerinde, tamsayı işaretsizse dönüştürme kaydırılır veya tamsayı imzalıysa -2147483648 olarak ayarlanır. Bu mimarilerden hiçbiri kayan nokta değerlerinin daha küçük tamsayı türlerine dönüştürülmesini doğrudan desteklemez; bunun yerine, dönüştürmeler 32 bit olarak gerçekleştirilir ve sonuçlar daha küçük bir boyuta kesilir.

ARM mimarisi için doygunluk ve kesme birleşimi, 32 bitlik bir tamsayıyı doygun hale getirince işaretsiz türlere dönüştürmenin daha küçük işaretsiz türleri doğru bir şekilde doygun hale getirmekte, ancak küçük türden daha büyük değerler için kesilmiş bir sonuç ürettiği, ancak tam 32 bit tamsayıyı doyamayacak kadar küçük olduğu anlamına gelir. Dönüştürme ayrıca 32 bit işaretli tamsayılar için de doğru doygunluk sağlar, ancak doygun, imzalı tamsayıların kesilmesi pozitif doygunluk değerleri için -1 ve negatif doygunluk değerleri için 0 sonucunu verir. Daha küçük bir imzalı tamsayıya dönüştürme işlemi, tahmin edilemeyen kesilmiş bir sonuç üretir.

x86 ve x64 mimarileri için, işaretsiz tamsayı dönüştürmeleri için sarmalama davranışı ve taşma üzerindeki imzalı tamsayı dönüştürmeleri için açık değerlemenin birleşimi, kesme ile birlikte, çoğu vardiyanın sonuçları çok büyükse tahmin edilemez hale getirir.

Bu platformlar, NaN'nin (Sayı Olmayan) tamsayı türlerine dönüştürülme yönteminde de farklılık gösterir. ARM'de NaN, 0x00000000 dönüştürür; x86 ve x64'te 0x80000000 dönüştürür.

Kayan nokta dönüştürme yalnızca değerin dönüştürülmekte olduğu tamsayı türü aralığında olduğunu biliyorsanız kullanılabilir.

Shift işleci (<<>>) davranışı

ARM mimarisinde, desen yinelenmeden önce bir değer 255 bit sola veya sağa kaydırılabilir. x86 ve x64 mimarilerinde, desenin kaynağı 64 bit değişken olmadığı sürece desen 32'nin her katı için yinelenir; bu durumda, desen x64 üzerinde 64'ün her katı ve bir yazılım uygulamasının kullanıldığı x86'da 256'nın her katı yineleniyor. Örneğin, 1 değeri 32 konum sola kaydırılmış bir 32 bit değişken için ARM'de sonuç 0, x86'da sonuç 1 ve x64'te sonuç da 1'dir. Ancak, değerin kaynağı 64 bit değişkense, üç platformdaki sonuç 4294967296 olur ve x64 üzerinde 64 veya ARM ve x86'da 256 konum kaydırılana kadar değer "kaydırmaz".

Kaynak türdeki bit sayısını aşan bir shift işleminin sonucu tanımsız olduğundan, derleyicinin her durumda tutarlı bir davranışa sahip olması gerekmez. Örneğin, derleme zamanında bir vardiyanın her iki işleneni de biliniyorsa, derleyici vardiyanın sonucunu önceden derlemek için bir iç yordam kullanarak ve sonra shift işleminin yerine sonucu değiştirerek programı iyileştirir. Vardiya miktarı çok büyük veya negatifse, iç yordamın sonucu CPU tarafından yürütülen kaydırma ifadesinin sonucundan farklı olabilir.

Değişken bağımsız değişkenleri (varargs) davranışı

ARM mimarisinde, yığına geçirilen değişken bağımsız değişkenler listesindeki parametreler hizalamaya tabidir. Örneğin, 64 bitlik bir parametre 64 bit sınıra hizalanır. x86 ve x64'te, yığında geçirilen bağımsız değişkenler hizalamaya tabi değildir ve sıkı bir şekilde paketlenmez. Bu fark, değişken bağımsız değişkenler listesinin beklenen düzeni tam olarak eşleşmediyse, x86 veya x64 mimarilerindeki bazı değerlerin bir alt kümesinde çalışsa bile ARM'de doldurma olarak amaçlanan bellek adreslerini okumak gibi printf bir variadic işlevine neden olabilir. Bu örneği ele alalım:

// notice that a 64-bit integer is passed to the function, but '%d' is used to read it.
// on x86 and x64 this may work for small values because %d will "parse" the low-32 bits of the argument.
// on ARM the calling convention will align the 64-bit value and the code will print a random value
printf("%d\n", 1LL);

Bu durumda hata, bağımsız değişkenin hizalamasının dikkate alınması için doğru biçim belirtiminin kullanıldığından emin olunarak düzeltilebilir. Bu kod doğrudur:

// CORRECT: use %I64d for 64-bit integers
printf("%I64d\n", 1LL);

Bağımsız değişken değerlendirme sırası

ARM, x86 ve x64 işlemcileri çok farklı olduğundan, derleyici uygulamalarına farklı gereksinimler ve iyileştirmeler için farklı fırsatlar sunabilirler. Bu nedenle, çağırma kuralı ve iyileştirme ayarları gibi diğer faktörlerle birlikte, derleyici farklı mimarilerde veya diğer faktörler değiştirildiğinde işlev bağımsız değişkenlerini farklı bir sırada değerlendirebilir. Bu, belirli bir değerlendirme sırasını kullanan bir uygulamanın davranışının beklenmedik şekilde değişmesine neden olabilir.

Bir işleve yönelik bağımsız değişkenlerin aynı çağrıdaki işleve yönelik diğer bağımsız değişkenleri etkileyen yan etkileri olduğunda bu tür bir hata oluşabilir. Genellikle bu tür bağımlılıklardan kaçınmak kolaydır, ancak bazen ayırt edilmesi zor olan bağımlılıklar veya işleç aşırı yüklemesi nedeniyle gizlenebilir. Şu kod örneğini göz önünde bulundurun:

handle memory_handle;

memory_handle->acquire(*p);

Bu iyi tanımlanmış görünür, ancak ve * aşırı yüklenmiş işleçlerse->, bu kod şuna benzer bir şeye çevrilir:

Handle::acquire(operator->(memory_handle), operator*(p));

ve operator*(p)arasında operator->(memory_handle) bir bağımlılık varsa, özgün kod olası bir bağımlılık yok gibi görünse bile kod belirli bir değerlendirme sırasına bağlı olabilir.

geçici anahtar sözcük varsayılan davranışı

MSVC derleyicisi, derleyici anahtarlarını kullanarak belirtebileceğiniz depolama niteleyicisinin iki farklı yorumunu volatile destekler. /volatile:ms anahtarı, bu mimarilerdeki güçlü bellek modeli nedeniyle x86 ve x64 için geleneksel durumda olduğu gibi güçlü sıralamayı garanti eden Microsoft genişletilmiş geçici semantiğini seçer. /volatile:iso anahtarı, güçlü sıralamayı garanti etmeyen katı C++ standart geçici semantiğini seçer.

ARM mimarisinde (ARM64EC hariç), ARM işlemcileri zayıf sıralı bir bellek modeline sahip olduğundan ve ARM yazılımının genişletilmiş /volatile:ms semantiğine dayalı eski bir geçmişi olmadığından ve genellikle bunu kullanan yazılımlarla arabirim oluşturmak zorunda olmadığından varsayılan değer /volatile:iso'dır. Ancak, genişletilmiş semantiği kullanmak için bir ARM programı derlemek bazen kullanışlı ve hatta gereklidir. Örneğin, ISO C++ semantiğini kullanmak için bir programı taşımanız çok maliyetli olabilir veya sürücü yazılımının düzgün çalışması için geleneksel semantiği kullanması gerekebilir. Bu gibi durumlarda /volatile:ms anahtarını kullanabilirsiniz; ancak ARM hedeflerinde geleneksel geçici semantiği yeniden oluşturmak için, güçlü sıralamayı zorlamak için derleyicinin bir volatile değişkenin her okuma veya yazma işlemine bellek engelleri eklemesi gerekir ve bu da performansı olumsuz etkileyebilir.

x86, x64 ve ARM64EC mimarilerinde varsayılan değer /volatile:ms'dir çünkü MSVC kullanılarak bu mimariler için önceden oluşturulmuş olan yazılımların çoğu bunlara dayanır. x86, x64 ve ARM64EC programları derlerken, geleneksel geçici semantiği gereksiz yere bağımlılıktan kaçınmak ve taşınabilirliği artırmak için /volatile:iso anahtarını belirtebilirsiniz.

Ayrıca bkz.

Visual C++’ı ARM işlemciler için yapılandırma