Konteks
Kami mem-porting kode C yang awalnya dikompilasi menggunakan kompiler C 8-bit untuk mikrokontroler PIC. Ungkapan umum yang digunakan untuk mencegah variabel global yang tidak ditandatangani (misalnya, penghitung kesalahan) berguling kembali ke nol adalah sebagai berikut:
if(~counter) counter++;
Operator bitwise di sini membalikkan semua bit dan pernyataan ini hanya benar jika counter
kurang dari nilai maksimum. Yang penting, ini berfungsi terlepas dari ukuran variabel.
Masalah
Kami sekarang menargetkan prosesor ARM 32-bit menggunakan GCC. Kami memperhatikan bahwa kode yang sama menghasilkan hasil yang berbeda. Sejauh yang kami tahu, sepertinya operasi komplemen bitwise mengembalikan nilai dengan ukuran yang berbeda dari yang kami harapkan. Untuk mereproduksi ini, kami mengkompilasi, di GCC:
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
Pada baris pertama output, kita mendapatkan apa yang kita harapkan: i
adalah 1 byte. Namun, komplemen bitwise i
sebenarnya empat byte yang menyebabkan masalah karena perbandingan dengan ini sekarang tidak akan memberikan hasil yang diharapkan. Misalnya, jika melakukan (di mana i
diinisialisasi dengan benar uint8_t
):
if(~i) i++;
Kita akan melihat i
"membungkus" dari 0xFF kembali ke 0x00. Perilaku ini berbeda dalam GCC dibandingkan dengan ketika dulu berfungsi seperti yang kami maksudkan dalam kompilator sebelumnya dan mikrokontroler PIC 8-bit.
Kami menyadari bahwa kami dapat menyelesaikan ini dengan casting seperti:
if((uint8_t)~i) i++;
Atau, oleh
if(i < 0xFF) i++;
Namun dalam kedua penyelesaian ini, ukuran variabel harus diketahui dan rawan kesalahan untuk pengembang perangkat lunak. Pemeriksaan batas atas semacam ini terjadi di seluruh basis kode. Ada beberapa ukuran variabel (mis., uint16_t
Dan unsigned char
lain - lain) dan mengubahnya dalam basis kode yang berfungsi bukan sesuatu yang kami harapkan.
Pertanyaan
Apakah pemahaman kita tentang masalah itu benar, dan apakah ada opsi yang tersedia untuk menyelesaikan ini yang tidak perlu mengunjungi kembali setiap kasus di mana kita telah menggunakan idiom ini? Apakah asumsi kami benar, bahwa operasi seperti pelengkap bitwise harus mengembalikan hasil yang ukurannya sama dengan operan? Sepertinya ini akan pecah, tergantung pada arsitektur prosesor. Saya merasa seperti saya minum pil gila dan C harus sedikit lebih portabel daripada ini. Sekali lagi, pemahaman kita tentang ini bisa salah.
Di permukaan ini mungkin tidak tampak seperti masalah besar tetapi idiom yang sebelumnya berfungsi ini digunakan di ratusan lokasi dan kami ingin memahami ini sebelum melanjutkan dengan perubahan mahal.
Catatan: Ada pertanyaan duplikat yang tampaknya serupa tetapi tidak tepat di sini: Operasi bitwise pada char memberikan hasil 32 bit
Saya tidak melihat inti sebenarnya dari masalah yang dibahas di sana, yaitu, ukuran hasil komplemen bitwise menjadi berbeda dari apa yang diteruskan ke operator.
sumber
Jawaban:
Apa yang Anda lihat adalah hasil dari promosi bilangan bulat . Dalam kebanyakan kasus di mana nilai integer digunakan dalam ekspresi, jika jenis nilai lebih kecil dari
int
nilai yang dipromosikanint
. Ini didokumentasikan dalam bagian 6.3.1.1p2 dari standar C :Jadi, jika suatu variabel memiliki tipe
uint8_t
dan nilai 255, menggunakan operator apa pun selain pemeran atau penugasan di atasnya terlebih dahulu akan mengubahnya untuk mengetikint
dengan nilai 255 sebelum melakukan operasi. Inilah sebabnya mengapasizeof(~i)
memberi Anda 4 bukannya 1.Bagian 6.5.3.3 menjelaskan bahwa promosi bilangan bulat berlaku untuk
~
operator:Jadi dengan asumsi 32 bit
int
, jikacounter
memiliki nilai 8 bit0xff
itu dikonversi ke nilai 32 bit0x000000ff
, dan menerapkannya~
memberi Anda0xffffff00
.Mungkin cara paling sederhana untuk menangani ini adalah tanpa harus tahu jenisnya adalah untuk memeriksa apakah nilainya 0 setelah bertambah, dan jika demikian kurangi.
Sampul bilangan bulat tak bertanda bekerja di kedua arah, sehingga penurunan nilai 0 memberi Anda nilai positif terbesar.
sumber
if (!++counter) --counter;
mungkin kurang aneh untuk beberapa programmer daripada menggunakan operator koma.++counter; counter -= !counter;
.increment_unsigned_without_wraparound
atauincrement_with_saturation
. Secara pribadi, saya akan menggunakanclamp
fungsi tri-operan generik .dalam sizeof (i); Anda meminta ukuran variabel i , jadi 1
dalam sizeof (~ i); Anda meminta ukuran jenis ekspresi, yang merupakan int , dalam kasus Anda 4
Menggunakan
untuk mengetahui apakah saya tidak menghargai 255 (dalam kasus Anda dengan uint8_t) tidak terlalu mudah dibaca, lakukan saja
dan Anda akan memiliki kode portabel dan mudah dibaca
Untuk mengelola ukuran yang tidak ditandatangani:
Ungkapannya konstan, jadi dihitung pada waktu kompilasi.
#include <Limit.h> untuk CHAR_BIT dan #include <stdint.h> untuk uintmax_t
sumber
!= 255
tidak memadai.unsigned
objek karena pergeseran lebar objek penuh tidak ditentukan oleh standar C, tetapi dapat diperbaiki dengan(2u << sizeof(i)*CHAR_BIT-1) - 1
.((uintmax_t) 2 << sizeof(i)*CHAR_BIT-1) - 1
.Berikut adalah beberapa opsi untuk menerapkan "Tambah 1 ke
x
tetapi jepit pada nilai representable maksimum," mengingat bahwax
ada beberapa jenis integer yang tidak ditandatangani:Tambahkan satu jika dan hanya jika
x
kurang dari nilai maksimum yang diwakili dalam jenisnya:Lihat item berikut untuk definisi
Maximum
. Metode ini berpeluang besar untuk dioptimalkan oleh kompiler ke instruksi yang efisien seperti perbandingan, beberapa bentuk set atau pemindahan kondisional, dan tambahan.Bandingkan dengan nilai terbesar dari jenis:
(Ini menghitung 2 N , di mana N adalah jumlah bit
x
, dengan menggeser 2 oleh N −1 bit. Kami melakukan ini alih-alih menggeser 1 N bit karena pergeseran oleh jumlah bit dalam jenis tidak ditentukan oleh C standar.CHAR_BIT
Makro mungkin asing bagi beberapa; itu adalah jumlah bit dalam satu byte, demikiansizeof x * CHAR_BIT
juga jumlah bit dalam jenisx
.)Ini dapat dibungkus dalam makro yang diinginkan untuk estetika dan kejelasan:
Bertambah
x
dan perbaiki jika membungkus ke nol, menggunakanif
:Bertambah
x
dan perbaiki jika membungkus ke nol, menggunakan ekspresi:Ini adalah tanpa cabang nominal (kadang-kadang bermanfaat untuk kinerja), tetapi kompiler dapat mengimplementasikannya sama seperti di atas, menggunakan cabang jika diperlukan tetapi mungkin dengan instruksi tanpa syarat jika arsitektur target memiliki instruksi yang sesuai.
Opsi tanpa cabang, menggunakan makro di atas, adalah:
Jika
x
maksimum jenisnya, ini dievaluasi menjadix += 1-1
. Kalau tidak demikianx += 1-0
. Namun, pembagian agak lambat pada banyak arsitektur. Kompiler dapat mengoptimalkan ini untuk instruksi tanpa divisi, tergantung pada kompiler dan arsitektur target.sumber
-Wshift-op-parentheses
. Berita baiknya adalah, kompiler pengoptimal tidak akan menghasilkan divisi di sini, jadi Anda tidak perlu khawatir itu lambat.sizeof x
tidak dapat diimplementasikan di dalam fungsi C karenax
harus menjadi parameter (atau ekspresi lainnya) dengan beberapa tipe tetap. Itu tidak dapat menghasilkan ukuran dari jenis argumen apa pun yang digunakan penelepon. Makro bisa.Sebelum stdint.h ukuran variabel dapat bervariasi dari kompiler ke kompiler dan tipe variabel aktual di C masih int, panjang, dll dan masih ditentukan oleh pembuat kompiler untuk ukurannya. Bukan asumsi standar atau target spesifik. Penulis kemudian perlu membuat stdint.h untuk memetakan dua dunia, yaitu tujuan stdint.h untuk memetakan uint_his yang untuk int, panjang, pendek.
Jika Anda porting kode dari kompiler lain dan menggunakan char, pendek, int, lama maka Anda harus melalui setiap jenis dan melakukan port sendiri, tidak ada jalan lain. Dan baik Anda berakhir dengan ukuran yang tepat untuk variabel, deklarasi berubah tetapi kode sebagai karya tertulis ....
atau ... menyediakan topeng atau tipografi secara langsung
Pada akhirnya jika Anda ingin kode ini berfungsi, Anda harus mem-port-nya ke platform baru. Pilihan Anda bagaimana caranya. Ya, Anda harus menghabiskan waktu untuk setiap kasus dan melakukannya dengan benar, jika tidak, Anda akan terus kembali ke kode ini yang bahkan lebih mahal.
Jika Anda mengisolasi tipe variabel pada kode sebelum porting dan berapa ukuran tipe variabel, maka mengisolasi variabel yang melakukan ini (harus mudah dipahami) dan mengubah deklarasi mereka menggunakan definisi stdint.h yang diharapkan tidak akan berubah di masa depan, dan Anda akan terkejut tetapi header yang salah digunakan kadang-kadang bahkan memasukkan cek sehingga Anda bisa tidur lebih nyenyak di malam hari
Dan sementara itu gaya pengkodean bekerja (if (~ counter) counter ++;), untuk portabilitas yang diinginkan sekarang dan di masa depan yang terbaik adalah menggunakan masker untuk secara khusus membatasi ukuran (dan tidak bergantung pada deklarasi), lakukan ini ketika kode ditulis di tempat pertama atau hanya menyelesaikan port dan kemudian Anda tidak perlu port ulang lagi hari lain. Atau untuk membuat kode lebih mudah dibaca maka lakukan jika x <0xFF lalu atau x! = 0xFF atau sesuatu seperti itu maka kompiler dapat mengoptimalkannya ke dalam kode yang sama dengan solusi-solusi ini, hanya membuatnya lebih mudah dibaca dan kurang berisiko. ...
Bergantung pada seberapa penting produk tersebut atau berapa kali Anda ingin mengirimkan tambalan / pembaruan atau menggulung truk atau berjalan ke laboratorium untuk memperbaiki masalah apakah Anda mencoba menemukan solusi cepat atau hanya menyentuh garis kode yang terpengaruh. jika hanya seratus atau sedikit yang tidak sebesar pelabuhan.
sumber
C 2011 Draf Online
Masalahnya adalah bahwa operan
~
sedang dipromosikanint
sebelum operator diterapkan.Sayangnya, saya tidak berpikir ada jalan keluar yang mudah untuk ini. Penulisan
tidak akan membantu karena promosi juga berlaku di sana. Satu-satunya hal yang dapat saya sarankan adalah menciptakan beberapa konstanta simbolis untuk maksimum nilai yang Anda inginkan yang objek untuk mewakili dan pengujian terhadap bahwa:
sumber
-1
tidak diperlukan, karena ini akan menyebabkan penghitung untuk menetap di 254 (0xFE). Bagaimanapun, pendekatan ini, seperti yang disebutkan dalam pertanyaan saya, tidak ideal karena ukuran variabel yang berbeda dalam basis kode yang berpartisipasi dalam idiom ini.