Gömülü kod için neden “imzasız int” yerine “uint_t” türlerini kullanmalıyım?


22

Gcc kullanarak bir STM32F105 c için bir uygulama yazıyorum.

Geçmişte (daha basit projeler ile), ben her zaman olduğu gibi değişkenleri tanımladık char, int, unsigned int, vb.

Bunun gibi stdint.h tanımlanan türde kullanımı yaygındır görüyoruz int8_t, uint8_t, uint32_tvb Bu çoklu API en kullanıyorum olduğunu ve aynı zamanda ST gelen ARM CMSIS kütüphanede gerçek.

Bunu neden yapmamız gerektiğini anladığına inanıyorum; derleyicinin bellek alanını daha iyi optimize etmesine izin vermek için. Umarım ek sebepler olabilir.

Ancak, c'nin tamsayı promosyon kuralları nedeniyle, iki değer eklemeye çalıştığımda, bit yönünde işlem yaptığımda, dönüştürme uyarılarına karşı koşmaya devam ediyorum. Uyarı gibi bir şey okuyor conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]. Mesele burada ve burada tartışılmaktadır .

intVeya olarak bildirilen değişkenleri kullanırken bu gerçekleşmez unsigned int.

Birkaç örnek vermek gerekirse:

uint16_t value16;
uint8_t value8;

Bunu değiştirmek zorunda kalacağım:

value16 <<= 8;
value8 += 2;

buna:

value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);

Çirkin, ama gerekirse yapabilirim. İşte benim sorularım:

  1. Dönüştürme bir vaka var mı imzasız için imzalanmış ve geri imzasız sonuç yanlış yapacak?

  2. Stdint.h tamsayı türlerini kullanmanın / buna karşı başka büyük nedenler var mı?

Stdint.h türleri genellikle tercih edilir gibi alıyorum yanıtlara göre, hatta c dönüştürür olsa görünüyor uintetmek intve arka. Bu daha büyük bir soruya yol açar:

  1. Derleyici uyarılarını typecasting (örn. value16 = (uint16_t)(value16 << 8);) Kullanarak önleyebilirim . Sadece sorunu gizliyor muyum? Bunun için daha iyi bir yol var mı?

İmzasız hazır bilgileri kullanın: yani 8uve 2u.
Monica'da

Teşekkürler, @OrangeDog, sanırım yanlış anlıyorum. İkisini de denedim value8 += 2u;ve value8 = value8 + 2u;aynı uyarıları alıyorum.
bitsmack

Zaten geniş
kapsamlı

Yanıtlar:


11

int17 - 32 bit arasında herhangi bir yerde bulunan standartlara uygun bir derleyici , yasal olarak aşağıdaki kodla istediği her şeyi yapabilir :

uint16_t x = 46341;
uint32_t y = x*x; // temp result is signed int, which can't hold 2147488281

Bunu yapmak isteyen bir uygulama, meşru bir şekilde, akla gelebilecek her protokolü kullanarak her bağlantı noktası pimine "Fred" dizesini basmak dışında hiçbir şey yapmayacak bir program oluşturabilirdi. Bir programın böyle bir şeyi yapacak bir uygulamaya aktarılması olasılığı son derece düşüktür, ancak teorik olarak mümkündür. Eğer yukarıdaki kodu Tanımsız Davranışa girmeme garantili olacak şekilde yazmak istenirse, sonraki ifadeyi (uint32_t)x*xveya olarak yazmanız gerekir 1u*x*x. int17 ile 31 bit arasındaki bir derleyicide , ikinci ifade üst bitlerden kopar, ancak Tanımsız Davranışa girmez.

Gcc uyarılarının muhtemelen yazılan kodun tamamen% 100 taşınabilir olmadığını ortaya koymaya çalıştığını düşünüyorum. Bazı uygulamalarda Tanımlanamayacak davranışlardan kaçınmak için kodun gerçekten yazılması gereken zamanlar vardır, ancak diğer birçok durumda, kodun aşırı can sıkıcı şeyler yapacak uygulamalarda kullanılma ihtimalinin düşük olduğunu düşünmesi gerekir.

Gibi intve shortkullanmanın bazı uyarıları ortadan kaldırabileceğini ve bazı sorunları çözebileceğini, ancak muhtemelen başkalarını yaratacağını unutmayın. uint16_tC ve integer-terfi kuralları gibi tipler arasındaki etkileşim çok hassastır, ancak bu tipler muhtemelen herhangi bir alternatiften daha iyidir.


6

1) Sadece aynı işaretsiz imzalı tamsayıdan aynı uzunluktaki imzalı tamsayıya çevirirseniz, aralarında herhangi bir işlem yapmazsanız, her seferinde aynı sonucu elde edersiniz, burada sorun yok. Ancak çeşitli mantıksal ve aritmetik işlemler imzalı ve imzasız işlenenler üzerinde farklı davranıyor.
2) stdint.hTürleri kullanmanın asıl nedeni , bu tür türlerin bit büyüklüğünün, tüm platformlar için tanımlanmış ve eşit olması, bunun için doğru değildir int, longvb char. varsayılan. Ekstra kontrol ve varsayımlar kullanmadan kesin boyutu bilen verileri manipüle etmeyi kolaylaştırır.


2
Boyutları int32_tve tanımlandıklarıuint32_t tüm platformlar arasında eşittir . İşlemci tam olarak eşleşen bir donanım türüne sahip değilse , bu türler tanımlanmaz. Dolayısıyla avantajı belki vb, vbintint_least32_t
Pete Becker

1
@PeteBecker - Sonuçta derleme hataları sizi derhal haberdar etmenize neden olduğu için muhtemelen bir avantaj. Türlerimin üzerimdeki büyüklüğünü değiştirmekten çok daha fazlasını istiyorum.
sapi

@sapi - çoğu durumda altta yatan boyut önemsizdir; C programcıları uzun yıllar boyunca sabit büyüklükte olmadan gayet iyi geçindiler.
Pete Becker,

6

Eugene # 2 muhtemelen en önemli nokta olduğundan, ben sadece bunun bir tavsiye olduğunu eklemek istiyorum

MISRA (directive 4.6): "typedefs that indicate size and signedness should be used in place of the basic types".

Ayrıca Jack Ganssle bu kuralın destekçisi gibi görünüyor: http://www.ganssle.com/tem/tem265.html


2
"Aynı boyutta bir sonuç elde etmek için başka herhangi bir aynı boyutta tamsayı ile güvenle çarpılabilen N-bit işaretsiz tamsayı" belirtmek için herhangi bir tür olmaması çok kötü. Tamsayı tanıtım kuralları, var olan türlerle korkunç biçimde etkileşime girer uint32_t.
supercat

3

Uyarıları ortadan kaldırmanın kolay bir yolu, GCC’de -Weversion kullanmaktan kaçınmaktır. Bu seçeneği manuel olarak etkinleştirmeniz gerektiğini düşünüyorum, ancak değilse, -Wno-dönüşümünü devre dışı bırakmak için kullanabilirsiniz. Hala isterseniz, işaret ve FP hassas dönüşümleri için diğer seçenekleri kullanarak uyarıları etkinleştirebilirsiniz .

-Wconversion uyarıları neredeyse her zaman yanlış pozitiflerdir, bu nedenle muhtemelen -Wextra'nın varsayılan olarak etkinleştirmemesini sağlar. Bir Yığın Taşması sorusunun iyi seçenek kümeleri için birçok önerisi vardır. Kendi tecrübelerime dayanarak, başlamak için iyi bir yer:

-std = c99 -perdantik -Duvar -Düğme -Duvar Örtüsü

İhtiyacınız olursa daha fazla ekleyin, ancak ihtimaliniz yoktur.

-Wconversion tuşunu tutmanız gerekiyorsa, kodunuzu yalnızca sayısal işleneni yazarak biraz kısaltabilirsiniz:

value16 <<= (uint16_t)8;
value8 += (uint8_t)2;

Yine de, sözdizimi vurgulamadan okunması kolay değildir.


2

Herhangi bir yazılım projesinde, taşınabilir tip tanımları kullanmak çok önemlidir. (Aynı derleyicinin bir sonraki sürümü bile bu düşünceye ihtiyaç duyar.) İyi bir örnek, birkaç yıl önce mevcut derleyicinin 'int' değerini 8 bit olarak tanımladığı bir projede çalıştım. Derleyicinin bir sonraki sürümü 'int' değerini 16 bit olarak tanımladı. 'İnt' için herhangi bir taşınabilir tanım kullanmadığımızdan, ram (etkin) iki katına çıktı ve 8bit int'ye bağlı birçok kod dizisi başarısız oldu. Portatif bir tip tanımın kullanılması bu sorunu ortadan kaldırabilirdi (düzeltmek için yüzlerce adam saati).


int8 bit türüne atıfta bulunmak için hiçbir makul kod kullanılmamalıdır . CCS gibi bir C olmayan bir derleyici böyle olsa bile, makul kod char8 bit için ya da typedefed bir tip ve 16 bit için typedefed bir tip ("uzun" değil) kullanmalıdır. Öte yandan, kodun CCS gibi bir şeyden gerçek bir derleyiciye taşınması, uygun tip tanımları kullansa bile sorunlu olmaya meyillidir, çünkü bu derleyiciler genellikle "olağandışı" dır.
Süperkat

1
  1. Evet. Bir n-bit işaretli tam sayı, bir n-bit işaretsiz tam sayı olarak negatif olmayan sayının sayısının kabaca yarısını temsil edebilir ve taşma özelliklerine güvenmek tanımsız bir davranıştır, böylece her şey olabilir. Mevcut ve geçmiş işlemcilerin büyük çoğunluğu iki tamamlayıcı kullanmaktadır, bu nedenle birçok işlem imzalanmış ve imzasız integral türlerinde aynı şeyi yapar, ancak o zaman bile tüm işlemler aynı sonuçlar elde edilmez. Kodunuzun neden amaçlanan şekilde çalışmadığını anlayamadığınızda daha sonra gerçekten fazladan sorun istiyorsunuz.

  2. İnt ve imzasız uygulamalarda tanımlanmış boyutlara sahip olsalar da , bunlar genellikle boyut veya hız nedenleriyle uygulama tarafından "akıllıca" seçilir. Aksi halde yapmak için iyi bir nedenim olmadıkça, genellikle bunlara sadık kalacağım. Aynı şekilde, int veya imzasız kullanıp kullanmadığımı düşünürken, aksi takdirde yapmak için iyi bir nedenim olmadığı sürece, genellikle int'yi tercih ederim.

Bir türün büyüklüğü veya imzası üzerinde gerçekten daha iyi kontrole ihtiyacım olduğu durumlarda, genellikle sistem tanımlı bir typedef (size_t, intmax_t, vb.) Kullanmayı ya da verilen bir fonksiyonun işlevini belirten kendi typedef'imi kullanmayı tercih ederim. türü (prng_int, adc_int, vb.).


0

Genellikle, kod ARM başparmak ve AVR'de (ve x86, powerPC ve diğer mimarilerde) kullanılır ve 16 veya 32 bit , STM32 ARM üzerinde 8 bitlik bir değişken için bile ( 16 yol ve flaş) ve daha verimli olabilir. 8 bit AVR'de daha verimlidir) . Bununla birlikte, eğer SRAM neredeyse doluysa, küresel değişkenler için 8 bit'e geri dönmek makul olabilir (ancak yerel değişkenler için değil). Taşınabilirlik ve bakım için (özellikle 8 bitlik değişkenler için), tam boyut yerine MINIMUM uygun boyut belirtmek, bir .h yerinde (genellikle ifdef altında) yazmak için (muhtemelen uint_fast8_t / uint_least8_t) taşıma / inşa süresi sırasında, örneğin:

// apparently uint16_t is just as efficient as 32 bit on STM32, but 8 bit is punished (with more flash and cycles)
typedef uint16_t uintG8_t; // 8bit if SRAM is scarce (use fol global vars that fit in 8 bit)
typedef uint16_t uintL8_t; // 8bit on AVR (local var, 16 or 32 bit is more efficient on STM + less flash)
// might better reserve 32 bits on some arch, STM32 seems efficient with 16 bits:
typedef uint16_t uintG16_t; // 16bit if SRAM is scarce (use fol global vars that fit in 16 bit)
typedef uint16_t uintL16_t; // 16bit on AVR (local var, 16 or 32 bit whichever is more efficient on other arch)

GNU kütüphanesi biraz yardımcı olur, ancak normalde typedef'ler yine de bir anlam ifade eder:

typedef uint_least8_t uintG8_t;
typedef uint_fast8_t uintL8_t;

// ancak SRAM kaygısı olmadığında YİD için uint_fast8_t.

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.