Untuk kode yang disematkan, mengapa saya harus menggunakan tipe “uint_t” alih-alih “unsigned int”?

22

Saya menulis aplikasi dalam c untuk STM32F105, menggunakan gcc.

Di masa lalu (dengan proyek-proyek sederhana), saya selalu didefinisikan sebagai variabel char, int, unsigned int, dan sebagainya.

Saya melihat bahwa itu adalah umum untuk menggunakan jenis didefinisikan dalam stdint.h, seperti int8_t, uint8_t, uint32_t, dll Hal ini benar dalam beberapa API yang saya gunakan, dan juga di perpustakaan ARM CMSIS dari ST.

Saya percaya bahwa saya mengerti mengapa kita harus melakukannya; untuk memungkinkan kompiler mengoptimalkan ruang memori dengan lebih baik. Saya berharap mungkin ada alasan tambahan.

Namun, karena aturan promosi bilangan bulat c, saya terus berjalan melawan peringatan konversi setiap kali saya mencoba menambahkan dua nilai, melakukan operasi bitwise, dll. Peringatan itu berbunyi seperti conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]. Masalahnya dibahas di sini dan di sini .

Itu tidak terjadi ketika menggunakan variabel dinyatakan sebagai intatau unsigned int.

Untuk memberikan beberapa contoh, berikan ini:

uint16_t value16;
uint8_t value8;

Saya harus mengubah ini:

value16 <<= 8;
value8 += 2;

untuk ini:

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

Itu jelek, tapi aku bisa melakukannya jika perlu. Ini pertanyaan saya:

  1. Apakah ada kasus di mana konversi dari tidak ditandatangani menjadi ditandatangani dan kembali ke tidak ditandatangani akan membuat hasilnya salah?

  2. Apakah ada alasan besar lainnya untuk / menentang penggunaan tipe integer stdint.h?

Berdasarkan jawaban yang saya terima, sepertinya jenis stdint.h umumnya lebih disukai, meskipun c mengkonversi uintke intdan kembali. Ini mengarah ke pertanyaan yang lebih besar:

  1. Saya bisa mencegah peringatan kompiler dengan menggunakan typecasting (mis value16 = (uint16_t)(value16 << 8);.). Apakah saya hanya menyembunyikan masalahnya? Apakah ada cara yang lebih baik untuk melakukannya?
bitmack
sumber
Gunakan literal yang tidak ditandatangani: yaitu 8udan 2u.
Stop Harming Monica
Terima kasih, @OrangeDog, saya pikir saya salah paham. Saya mencoba keduanya value8 += 2u;dan value8 = value8 + 2u;, tetapi saya mendapatkan peringatan yang sama.
bitsmack
Tetap gunakan itu, untuk menghindari peringatan yang ditandatangani ketika Anda belum memiliki peringatan lebar :)
Stop Harming Monica

Jawaban:

11

Kompiler yang memenuhi standar di mana intsaja dari 17 hingga 32 bit dapat melakukan apa pun yang diinginkan dengan kode berikut:

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

Sebuah implementasi yang ingin melakukannya dapat secara sah menghasilkan program yang tidak akan melakukan apa pun kecuali mengeluarkan string "Fred" berulang kali pada setiap port pin menggunakan setiap protokol yang bisa dibayangkan. Peluang suatu program untuk porting ke implementasi yang akan melakukan hal seperti itu sangat rendah, tetapi secara teori dimungkinkan. Jika ingin menulis kode di atas sehingga dijamin tidak terlibat dalam Perilaku Tidak Terdefinisi, perlu dituliskan ekspresi yang terakhir sebagai (uint32_t)x*xatau 1u*x*x. Pada kompiler di mana intantara 17 dan 31 bit, ekspresi terakhir akan memotong bit atas, tetapi tidak akan terlibat dalam Perilaku Tidak Terdefinisi.

Saya pikir peringatan gcc mungkin mencoba menyarankan bahwa kode yang ditulis tidak sepenuhnya portabel 100%. Ada saat-saat ketika kode benar-benar harus ditulis untuk menghindari perilaku yang tidak terdefinisi pada beberapa implementasi, tetapi dalam banyak kasus yang lain orang hanya perlu mengetahui bahwa kode tersebut tidak mungkin digunakan pada implementasi yang akan melakukan hal-hal yang terlalu mengganggu.

Perhatikan bahwa menggunakan jenis suka intdan shortmungkin menghilangkan beberapa peringatan dan memperbaiki beberapa masalah, tetapi kemungkinan akan membuat yang lain. Interaksi antara tipe like uint16_tdan aturan integer-promotion C memang menjengkelkan, tetapi tipe seperti itu mungkin masih lebih baik daripada alternatif apa pun.

supercat
sumber
6

1) Jika Anda baru saja beralih dari bilangan bulat ditandatangani ke bilangan bulat dengan panjang yang sama bolak-balik, tanpa operasi di antaranya, Anda akan mendapatkan hasil yang sama setiap kali, jadi tidak ada masalah di sini. Tetapi berbagai operasi logis dan aritmetika bertindak berbeda pada operan yang ditandatangani dan tidak ditandatangani.
2) Alasan utama untuk menggunakan stdint.hjenis adalah bahwa ukuran bit dari jenis tersebut didefinisikan dan sama di semua platform, yang tidak benar untuk int, longdll, serta chartidak memiliki tanda standar, dapat ditandatangani atau tidak ditandatangani oleh standar. Itu membuat lebih mudah untuk memanipulasi data mengetahui ukuran yang tepat tanpa menggunakan pemeriksaan dan asumsi tambahan.

Eugene Sh.
sumber
2
Ukuran int32_tdan uint32_tsama di semua platform di mana mereka didefinisikan . Jika prosesor tidak memiliki jenis perangkat keras yang sama persis , jenis ini tidak ditentukan. Karenanya keuntungan dari intdll. Dan, mungkin, int_least32_tdll.
Pete Becker
1
@PeteBecker - Itu bisa dibilang keuntungan, karena kesalahan kompilasi yang dihasilkan membuat Anda segera menyadari masalah. Saya lebih suka bahwa daripada tipe saya mengubah ukuran pada saya.
sapi
@sapi - dalam banyak situasi ukuran yang mendasarinya tidak relevan; Pemrogram C bergaul baik-baik saja tanpa ukuran tetap selama bertahun-tahun.
Pete Becker
6

Karena Eugene's # 2 mungkin adalah poin yang paling penting, saya hanya ingin menambahkan bahwa ini adalah sebuah nasihat

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

Jack Ganssle juga tampaknya menjadi pendukung aturan itu: http://www.ganssle.com/tem/tem265.html

Tom L.
sumber
2
Sayang sekali tidak ada tipe untuk menentukan "N-bit unsigned integer yang dapat dikalikan dengan aman oleh integer ukuran sama lainnya untuk menghasilkan hasil ukuran yang sama". Aturan promosi bilangan bulat berinteraksi mengerikan dengan tipe yang ada seperti uint32_t.
supercat
3

Cara mudah untuk menghilangkan peringatan adalah dengan menghindari penggunaan -Wversi di GCC. Saya pikir Anda harus mengaktifkan opsi ini secara manual, tetapi jika tidak, Anda dapat menggunakan -Wno-konversi untuk menonaktifkannya. Anda dapat mengaktifkan peringatan untuk konversi presisi tanda dan FP melalui opsi lain , jika Anda menginginkannya.

Peringatan-konversi hampir selalu positif palsu, yang mungkin mengapa tidak -Wextra mengaktifkannya secara default. Pertanyaan Stack Overflow memiliki banyak saran untuk set opsi yang baik. Berdasarkan pengalaman saya sendiri, ini adalah tempat yang baik untuk memulai:

-std = c99 -pedantic -Wall -Wextra -Wshadow

Tambahkan lebih banyak jika Anda membutuhkannya, tetapi kemungkinan besar tidak.

Jika Anda harus menjaga -Konversi, Anda dapat mempersingkat kode sedikit dengan hanya mengetikkan operan angka:

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

Itu tidak mudah dibaca tanpa penyorotan sintaks.

Adam Haun
sumber
2

dalam proyek perangkat lunak apa pun, sangat penting untuk menggunakan definisi tipe portabel. (bahkan versi berikutnya dari kompiler yang sama perlu pertimbangan ini.) Contoh yang baik, beberapa tahun yang lalu saya mengerjakan proyek di mana kompiler saat ini mendefinisikan 'int' sebagai 8bits. Versi selanjutnya dari kompiler mendefinisikan 'int' sebagai 16bits. Karena kami tidak menggunakan definisi portabel untuk 'int', ram (secara efektif) menggandakan ukuran dan banyak urutan kode yang bergantung pada int 8bit gagal. Penggunaan definisi tipe portabel akan menghindari masalah (ratusan jam kerja untuk memperbaiki).

Richard Williams
sumber
Tidak ada kode masuk akal yang harus digunakan intuntuk merujuk pada tipe 8 bit. Bahkan jika kompiler non-C seperti CCS melakukannya, kode yang masuk akal harus menggunakan salah satu charatau tipe yang diketikkan untuk 8 bit, dan tipe yang diketikkan (tidak "panjang") untuk 16 bit. Di sisi lain, kode porting dari sesuatu seperti CCS ke kompiler nyata cenderung bermasalah bahkan jika menggunakan typedef yang tepat, karena kompiler seperti itu sering "tidak biasa" dengan cara lain.
supercat
1
  1. Iya nih. Integer bertanda n-bit dapat mewakili kira-kira setengah dari angka non-negatif sebagai integer n-bit yang tidak ditandatangani, dan bergantung pada karakteristik overflow adalah perilaku yang tidak terdefinisi sehingga segala sesuatu dapat terjadi. Sebagian besar prosesor saat ini dan yang lalu menggunakan dua komplemen sehingga banyak operasi kebetulan melakukan hal yang sama pada tipe integral yang ditandatangani dan tidak ditandatangani, tetapi meskipun demikian tidak semua operasi akan menghasilkan hasil yang identik sedikit-bijaksana. Anda benar-benar meminta masalah tambahan nanti ketika Anda tidak tahu mengapa kode Anda tidak berfungsi sebagaimana dimaksud.

  2. Walaupun int dan unsigned memiliki ukuran implementasi yang ditentukan, ini sering dipilih "secara cerdas" oleh implementasi baik untuk ukuran atau alasan kecepatan. Saya biasanya tetap pada ini kecuali saya memiliki alasan yang baik untuk melakukan yang sebaliknya. Demikian juga, ketika mempertimbangkan apakah akan menggunakan int atau tidak, saya biasanya lebih suka int kecuali saya memiliki alasan yang baik untuk melakukannya.

Dalam kasus di mana saya benar-benar membutuhkan kontrol yang lebih baik atas ukuran atau keabsahan suatu jenis, saya biasanya akan lebih suka menggunakan typedef yang ditentukan sistem (size_t, intmax_t, dll.) Atau membuat typedef saya sendiri yang menunjukkan fungsi yang diberikan ketik (prng_int, adc_int, dll.)

helloworld922
sumber
0

Seringkali kode ini digunakan pada ARM jempol dan AVR (dan x86, powerPC dan arsitektur lainnya), dan 16 atau 32 bit dapat lebih efisien (kedua cara: flash dan siklus) pada STM32 ARM bahkan untuk variabel yang sesuai dalam 8 bit ( 8 bit lebih efisien pada AVR) . Namun jika SRAM hampir penuh, kembali ke 8 bit untuk vars global bisa masuk akal (tetapi tidak untuk vars lokal). Untuk portabilitas dan perawatan (terutama untuk vars 8 bit), ia memiliki kelebihan (tanpa kerugian) untuk menentukan ukuran MINIMUM yang sesuai , daripada ukuran yang tepat dan mengetikkan di satu tempat. uint_least8_t) selama porting / waktu pembuatan, misalnya:

// 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)

Perpustakaan GNU sedikit membantu, tetapi biasanya typedef masuk akal:

typedef uint_least8_t uintG8_t;
typedef uint_fast8_t uintL8_t;

// tapi uint_fast8_t untuk KEDUA ketika SRAM bukan masalah.

Marcell
sumber