Bitsel işlem beklenmedik değişken boyutuna neden olur


24

bağlam

Başlangıçta PIC mikrodenetleyici için 8 bit C derleyicisi kullanılarak derlenen C kodunu taşıyoruz. İmzasız global değişkenlerin (örneğin, hata sayaçları) sıfıra geri dönmesini önlemek için kullanılan yaygın bir deyim şudur:

if(~counter) counter++;

Bitsel operatör burada tüm bitleri tersine çevirir ve ifade sadece countermaksimum değerden azsa doğrudur . Daha da önemlisi, bu değişken büyüklüğünden bağımsız olarak çalışır.

Sorun

Şimdi GCC kullanarak 32 bit ARM işlemcisini hedefliyoruz. Aynı kodun farklı sonuçlar verdiğini fark ettik. Anlayabildiğimiz kadarıyla, bitsel tamamlayıcı işleminin beklediğimizden farklı bir değer döndürdüğü anlaşılıyor. Bunu çoğaltmak için GCC'de derliyoruz:

uint8_t i = 0;
int sz;

sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1

sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4

İlk çıktı satırında, beklediğimiz şeyi elde ederiz: i1 byte. Bununla birlikte, bitsel tamamlayıcısı iaslında dört bayttır, bu da soruna neden olur, çünkü şimdi bununla karşılaştırmalar beklenen sonuçları vermeyecektir. Örneğin, ( iuygun şekilde başlatılmışsa uint8_t) yapıyorsa :

if(~i) i++;

i0xFF'den 0x00'e kadar "sarın" göreceğiz . Bu davranış GCC'de önceki derleyicide ve 8 bit PIC mikro denetleyicide amaçladığımız gibi çalıştığından farklıdır.

Bunu şu şekilde döküm yaparak çözebileceğimizin farkındayız:

if((uint8_t)~i) i++;

Veya tarafından

if(i < 0xFF) i++;

Ancak bu geçici çözümlerin her ikisinde de değişkenin boyutu bilinmelidir ve yazılım geliştiricisi için hataya açıktır. Bu tür üst sınır kontrolleri kod tabanı boyunca gerçekleşir. Orada değişkenlerin çoklu boyutları (örn., uint16_tVe unsigned charvb) ve bir başka çalışma kod temeli bu değişen biz gözle bekliyoruz şey değildir.

Soru

Sorunu anlamamız doğru mu ve bunu çözmek için bu deyimi kullandığımız her vakayı tekrar ziyaret etmenizi gerektirmeyen seçenekler var mı? Bitsel tamamlayıcı gibi bir işlemin işlenenle aynı boyutta bir sonuç döndürmesi gerektiği varsayımımız doğru mu? İşlemci mimarilerine bağlı olarak bunun kırılacağı anlaşılıyor. Çılgın haplar alıyorum ve C'nin bundan biraz daha taşınabilir olması gerektiğini hissediyorum. Yine, bu anlayışımız yanlış olabilir.

Yüzeyde bu büyük bir sorun gibi görünmeyebilir, ancak daha önce çalışan bu deyim yüzlerce yerde kullanılıyor ve pahalı değişikliklere devam etmeden önce bunu anlamaya istekliyiz.


Not: Görünüşte benzer ancak tam olarak yinelenmeyen bir soru var: Char'da bitsel işlem 32 bit sonuç veriyor

Burada tartışılan konunun asıl sorununu görmedim, yani bitsel bir tamamlayıcının sonuç büyüklüğü operatöre geçirilenden farklıydı.


14
"Bitsel tamamlayıcı gibi bir işlemin işlenenle aynı boyutta bir sonuç döndürmesi gerektiği varsayımımız doğru mu?" Hayır, bu doğru değil, tamsayı promosyonları geçerlidir.
Thomas Jager

2
Kesinlikle alakalı olsa da, bunların bu sorunun çoğaltılmış olduğuna ikna olmadım, çünkü soruna bir çözüm sunmuyorlar.
Cody Gray

3
Çılgın haplar alıyorum ve C'nin bundan biraz daha taşınabilir olması gerektiğini hissediyorum. 8 bitlik türlerde tamsayı tanıtımları almadıysanız, derleyiciniz C standardıyla uyumlu değildi. Bu durumda, bunları kontrol etmek ve gerekirse düzeltmek için tüm hesaplamalardan geçmeniz gerektiğini düşünüyorum .
user694733

1
Gerçekten önemsiz sayaçların dışında, hangi mantığın "yeterince yer varsa arttırabilir, başka bir şey unutabilir" diye merak eden tek kişi ben miyim? Kod taşıyorsanız, uint_8 yerine int (4 bayt) kullanabilir misiniz? Bu, birçok durumda sorununuzu önleyecektir.
pak

1
@puck Haklısınız, 4 bayta değiştirebiliriz, ancak mevcut sistemlerle iletişim kurarken uyumluluğu bozar. Niyet olduğunda bilmektir herhangi hatalar ve böylece 1 bayt sayaç başlangıçta yeterli olduğunu ve kalıntılar bu yüzden.
Charlie Salts

Yanıtlar:


26

Gördüğünüz, tamsayı tanıtımların sonucudur . Bir ifadede bir tamsayı değerinin kullanıldığı çoğu durumda, değerin türü intdeğerin yükseltildiği değerden küçükse int. Bu, C standardının 6.3.1.1p2 bölümünde belgelenmiştir. :

Aşağıdakiler, bir ifadenin intveya unsigned int kullanılabilmektedir

  • Tamsayı dönüştürme sırası ( intveya dışında unsigned int) tamsayı türüne sahip bir nesne veya ifadeint ve unsigned int.
  • Bit alanı _Bool, int ,imzalı int, or imzalı int`.

A int, orijinal türün tüm değerlerini temsil ediyorsa (bir bit alanı için genişlikle sınırlandırıldığı gibi), değer int; aksi takdirde, bir unsigned int. Bunlara tamsayı tanıtımları denir . Diğer tüm türler tamsayı tanıtımları ile değişmez.

Dolayısıyla, bir değişkenin türü uint8_tve 255 değeri varsa, bir döküm veya atama dışında herhangi bir işleç kullanmak ilk önce onu türe dönüştürürint işlemi gerçekleştirmeden önce 255 değeriyle . Bu yüzden sizeof(~i)1 yerine 4 verir.

Bölüm 6.5.3.3, tamsayı tanıtımların ~ operatör :

Sonucu ~operatörü bit düzeyinde bir tamamlayıcıdır ve onun (tanıtılan) işlenen (olduğunu, sonuç olarak her bir bit ve dönüştürülmüş işlenen karşılık gelen bit ayarlanmamış sadece eğer ayarlanmış). Tamsayı yükseltmeleri işlenende gerçekleştirilir ve sonuç yükseltilen türe sahiptir. Tanıtılan tür işaretsiz bir türse, ifade ~Ebu tür eksi içinde temsil edilebilecek maksimum değere eşittir E.

Yani bir 32 bit varsayarak inteğer, counter8 bitlik değeri olan 0xff32 bitlik değerine dönüştürülür 0x000000ffve uygulamak ~size senkronizasyon için 0xffffff00.

Muhtemelen bunu halletmenin en basit yolu, türün bilinmesine gerek kalmadan, değerin arttıktan sonra 0 olup olmadığını kontrol etmek ve eğer öyleyse azaltmaktır.

if (!++counter) counter--;

İmzasız tam sayıların sarmalaması her iki yönde de çalışır, bu nedenle 0 değerini azaltmak size en büyük pozitif değeri verir.


1
if (!++counter) --counter;bazı programcılar için virgül operatörünü kullanmaktan daha az tuhaf olabilir.
Eric Postpischil

1
Başka bir alternatif ++counter; counter -= !counter;.
Eric Postpischil

@EricPostpischil Aslında, ilk seçeneğinizi daha çok seviyorum. Düzenlenen.
dbush

15
Nasıl yazdığın önemli değil, bu çirkin ve okunaksız. Böyle bir deyim kullanmanız gerekiyorsa, her bakım programlayıcısına bir iyilik yapın ve satır içi işlev olarak tamamlayın : increment_unsigned_without_wraparoundveya gibi bir şey increment_with_saturation. Şahsen, genel bir tri-operand clampişlevi kullanırdım.
Cody Gray

5
Ayrıca, bunu bir işlev haline getiremezsiniz, çünkü farklı argüman türleri için farklı davranması gerekir. Tip-jenerik bir makro kullanmanız gerekir .
user2357112 Monica

7

içerisinde sizeof (i) değişken i boyutunu talep , yani 1

içinde (~ i) sizeof'un; durumunuzda bir int olan ifadenin türünün boyutunu istersiniz 4


Kullanmak

(i ~)

olmadığını bilmek i (bir uint8_t ile söz konusu olduğunda) 255 değer vermediği sadece bunu değil, çok okunabilir

if (i != 255)

taşınabilir ve okunabilir bir kodunuz olacak


Birden çok değişken boyutu vardır (örneğin, uint16_t ve imzasız karakter vb.)

İmzasız herhangi bir boyutu yönetmek için:

if (i != (((uintmax_t) 2 << (sizeof(i)*CHAR_BIT-1)) - 1))

İfade sabittir, bu nedenle derleme zamanında hesaplanır.

#include <limits.h> için CHAR_BIT ve #include <stdint.h> için uintmax_t


3
Soru açıkça başa çıkmak için birden fazla boyuta sahip olduklarını belirtmektedir, bu yüzden != 255yetersizdir.
Eric Postpischil

@EricPostpischil ah evet, bunu unutuyorum, öyleyse "if (i! = ((1u << sizeof (i) * 8) - 1))" her zaman imzasız varsayalım?
bruno

1
unsignedTam nesne genişliğinin kaydırılması C standardı tarafından tanımlanmadığından, ancak nesnelerle sabitlenebildiğinden, bu nesneler için tanımsız olacaktır (2u << sizeof(i)*CHAR_BIT-1) - 1.
Eric Postpischil

oh evet ofc, CHAR_BIT, benim kötü
bruno

2
Daha geniş tiplerde güvenlik için, biri kullanılabilir ((uintmax_t) 2 << sizeof(i)*CHAR_BIT-1) - 1.
Eric Postpischil

5

Bazı işaretsiz tamsayı türü olduğu için “1'e ekleyin, xancak temsil edilebilecek maksimum değere kelepçe ekleyin” için birkaç seçenek vardır x:

  1. Yalnızca xtüründe temsil edilebilecek maksimum değerden küçükse ekleyin :

    x += x < Maximum(x);

    Tanımı için aşağıdaki öğeye bakın Maximum. Bu yöntem, bir derleyici tarafından karşılaştırma, bir tür koşullu küme veya taşıma biçimi ve ekleme gibi verimli talimatlara göre optimize edilme şansı yüksektir.

  2. Türün en büyük değeriyle karşılaştırın:

    if (x < ((uintmax_t) 2u << sizeof x * CHAR_BIT - 1) - 1) ++x

    (Bu , 2 N'yi hesaplar , burada N , bit sayısıdır, x2'yi N −1 bit kaydırır . Bunu 1 N biti kaydırmak yerine yaparız çünkü bir türdeki bit sayısına göre bir kaydırma C tarafından tanımlanmamıştır standardı. CHAR_BITmakro bazı yabancı olabilir, bu nedenle, bir bayt bit sayısı olan sizeof x * CHAR_BITbir türü bit sayısıdır x.)

    Bu, estetik ve netlik için istendiği gibi bir makroya sarılabilir:

    #define Maximum(x) (((uintmax_t) 2u << sizeof (x) * CHAR_BIT - 1) - 1)
    if (x < Maximum(x)) ++x;
  3. Aşağıdakileri xkullanarak sıfıra sarılırsa artırın ve düzeltin if:

    if (!++x) --x; // !++x is true if ++x wraps to zero.
  4. xBir ifade kullanarak sıfıra sarılırsa artırın ve düzeltin:

    ++x; x -= !x;

    Bu nominal olarak dalsızdır (bazen performans için faydalıdır), ancak bir derleyici bunu gerektiğinde bir dal kullanarak, ancak hedef mimarinin uygun talimatları varsa muhtemelen koşulsuz talimatlarla uygulayabilir.

  5. Yukarıdaki makroyu kullanan dalsız bir seçenek:

    x += 1 - x/Maximum(x);

    Türünün xmaksimum değeri ise, bu olarak değerlendirilir x += 1-1. Aksi halde, öyle x += 1-0. Ancak, bölünme birçok mimaride biraz yavaştır. Bir derleyici, derleyiciye ve hedef mimariye bağlı olarak bunu bölme olmadan talimatlara göre optimize edebilir.


1
Kendimi makro kullanmayı öneren bir yanıtı oylamaya getiremiyorum. C satır içi işlevlere sahiptir. Bu makro tanımının içinde, satır içi işlev içinde kolayca yapılamayacak hiçbir şey yapmıyorsunuz. Bir makro kullanacaksanız, netlik için stratejik olarak parantez içine aldığınızdan emin olun: operatör << çok düşük önceliğe sahiptir. Clang bu konuda uyarıyor -Wshift-op-parentheses. İyi haber, bir optimize derleyici edilir değil bunu yavaş olmakla ilgili endişe zorunda kalmamak için, burada bir bölünme oluşturmak için gidiyor.
Cody Gray

1
@CodyGray, bunu bir işlevle yapabileceğinizi düşünüyorsanız, bir cevap yazın.
Carsten S

2
@CodyGray: bir sabit sizeof xfonksiyona xsahip bir parametre (veya başka bir ifade) olması gerekeceğinden, bir C fonksiyonu içinde uygulanamaz . Arayanın kullandığı argüman türünün boyutunu üretemedi. Bir makro olabilir.
Eric Postpischil

2

Stdint.h'den önce değişken boyutları derleyiciden derleyiciye değişebilir ve C'deki gerçek değişken türleri hala int, long vb. Şeklindedir ve derleyici yazarı tarafından boyutlarına göre tanımlanır. Bazı standart veya hedefe özgü varsayımlar değil. Daha sonra yazar (lar) ın iki dünyayı eşleştirmek için stdint.h, yani stdint.h amacı bu uint_this'i int, long, short ile eşleştirmek için yaratmaları gerekir.

Başka bir derleyiciden kod taşıyorsanız ve char, short, int, long kullanıyorsa, her türden geçmeniz ve bağlantı noktasını kendiniz yapmanız gerekir, bunun etrafında bir yol yoktur. Ve ya değişken için doğru boyut ile sonuçlanır, bildirim değişir ama yazılı olarak kod çalışır ....

if(~counter) counter++;

veya ... maskeyi veya daktiloyu doğrudan sağlayın

if((~counter)&0xFF) counter++;
if((uint_8)(~counter)) counter++;

Günün sonunda bu kodun çalışmasını istiyorsanız, onu yeni platforma taşımalısınız. Nasıl yapacağınız konusunda seçiminiz. Evet, her vakayı isabet ettirmek ve doğru yapmak için zaman harcamanız gerekiyor, aksi takdirde daha pahalı olan bu koda geri dönmeye devam edeceksiniz.

Kod üzerindeki değişken türlerini taşımadan önce ve değişken türlerinin boyutlarını ayırırsanız, bunu yapan değişkenleri ayırın (grep olması kolay olmalıdır) ve bildirimlerini gelecekte değişmeyecek stdint.h tanımlarını kullanarak değiştirin, şaşıracaksınız ama bazen yanlış başlıklar kullanılıyor, bu yüzden geceleri daha iyi uyuyabilmeniz için kontroller bile yapıyorsunuz

if(sizeof(uint_8)!=1) return(FAIL);

Ve bu kodlama stili ((~ counter) counter ++;) çalışıyor olsa da, taşınabilirlik arzuları için şimdi ve gelecekte boyutu özel olarak sınırlamak (ve bildirime güvenmemek) için bir maske kullanmak en iyisidir. kod ilk etapta yazılır ya da sadece limanı bitirir ve daha sonra başka bir gün tekrar portlamak zorunda kalmazsınız. Ya da kodu daha okunabilir hale getirmek için, eğer x <0xFF sonra veya x! = 0xFF ya da bunun gibi bir şey yapın, derleyici bunu bu çözümlerden herhangi biri için aynı koda optimize edebilir, sadece daha okunabilir ve daha az riskli hale getirir ...

Ürünün ne kadar önemli olduğuna ya da kaç kez yamalar / güncellemeler göndermek istediğinize veya bir kamyonu yuvarlamak ya da hızlı bir çözüm bulmaya çalıştığınız ya da sadece etkilenen kod satırlarına dokunup dokunmadığınızı düzeltmek için laboratuvara doğru yürüdüğünüze bağlıdır. eğer sadece yüzlerce veya daha az ise bu bir limanın o kadar büyük değil.


0
6.5.3.3 Tekli aritmetik işleçler
...
4 İşlecin sonucu ~, (yükseltilmiş) işlenmesinin bitsel tamamlayıcısıdır (yani, sonuçtaki her bit, yalnızca dönüştürülen işlenendeki karşılık gelen bit ayarlanmamışsa ayarlanır. ). Tamsayı yükseltmeleri işlenende gerçekleştirilir ve sonuç yükseltilen türe sahiptir . Tanıtılan tür işaretsiz bir türse, ifade ~Ebu tür eksi içinde temsil edilebilecek maksimum değere eşittir E.

C 2011 Çevrimiçi Taslak

Sorun, operatörün uygulanmasından önce işleneni ~terfi intettirmesidir.

Ne yazık ki, bundan kolay bir yol olduğunu sanmıyorum. yazı

if ( counter + 1 ) counter++;

yardımcı olmayacaktır çünkü promosyonlar orada da geçerlidir. Önerebileceğim tek şey, o nesnenin temsil etmesini istediğiniz maksimum değer için bazı sembolik sabitler oluşturmak ve buna karşı test etmektir:

#define MAX_COUNTER 255
...
if ( counter < MAX_COUNTER-1 ) counter++;

Tamsayı tanıtımıyla ilgili noktayı takdir ediyorum - bu, karşılaştığımız sorun gibi görünüyor. Bununla birlikte, değinmeye değer, ikinci kod örneğinizde -1gerekli değildir, çünkü sayacın 254'e (0xFE) yerleşmesine neden olur. Her durumda, bu yaklaşım, sorumda belirtildiği gibi, bu deyime katılan kod tabanındaki farklı değişken boyutları nedeniyle ideal değildir.
Charlie Salts
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.