Dalam jawaban ini , zwol membuat klaim ini:
Cara yang benar untuk mengkonversi dua byte data dari sumber eksternal menjadi integer bertanda 16-bit adalah dengan fungsi pembantu seperti ini:
#include <stdint.h>
int16_t be16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 8) |
(((uint32_t)data[1]) << 0);
return ((int32_t) val) - 0x10000u;
}
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 0) |
(((uint32_t)data[1]) << 8);
return ((int32_t) val) - 0x10000u;
}
Manakah dari fungsi di atas yang sesuai tergantung pada apakah array berisi representasi endian kecil atau besar. Endianness bukan masalah yang dipertanyakan di sini, saya bertanya-tanya mengapa zwol mengurangi 0x10000u
dari uint32_t
nilai yang dikonversi int32_t
.
Mengapa ini cara yang benar ?
Bagaimana cara menghindari perilaku yang ditentukan implementasi ketika mengkonversi ke tipe kembali?
Karena Anda dapat mengasumsikan representasi komplemen 2's, bagaimana gips sederhana ini gagal: return (uint16_t)val;
Apa yang salah dengan solusi naif ini:
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
c
casting
language-lawyer
chqrlie
sumber
sumber
int16_t
didefinisikan, sehingga pendekatan naif tidak mudah dibawa-bawa.int16_t
0xFFFF0001u
tidak dapat direpresentasikan sebagaiint16_t
, dan dalam pendekatan kedua0xFFFFu
tidak dapat direpresentasikan sebagaiint16_t
.Jawaban:
Jika
int
16-bit maka versi Anda bergantung pada perilaku yang ditentukan implementasi jika nilai ekspresi dalamreturn
pernyataan di luar kisaranint16_t
.Namun versi pertama juga memiliki masalah serupa; misalnya jika
int32_t
adalah typedef untukint
, dan byte input keduanya0xFF
, maka hasil pengurangan dalam pernyataan kembali adalahUINT_MAX
yang menyebabkan perilaku yang ditentukan implementasi ketika dikonversi keint16_t
.IMHO jawaban yang Anda tautkan memiliki beberapa masalah besar.
sumber
int16_t
?uchar8_t
.Ini harus benar pedantically dan bekerja juga pada platform yang menggunakan bit tanda atau representasi komplemen 1 , alih-alih pelengkap 2 yang biasa . Input byte dianggap sebagai komplemen 2's.
Karena cabang, itu akan lebih mahal daripada opsi lain.
Apa yang dicapai ini adalah bahwa ia menghindari asumsi tentang bagaimana
int
keterwakilan berhubunganunsigned
representasi pada platform. Para pemainint
diharuskan untuk mempertahankan nilai aritmatika untuk nomor apa pun yang sesuai dengan tipe target. Karena inversi memastikan bit top dari angka 16-bit akan menjadi nol, nilainya akan pas. Kemudian unary-
dan pengurangan 1 menerapkan aturan biasa untuk negasi komplemen 2's. Tergantung pada platform,INT16_MIN
masih bisa meluap jika tidak sesuai denganint
tipe pada target, dalam hal inilong
harus digunakan.Perbedaan ke versi asli dalam pertanyaan muncul di waktu pengembalian. Sementara yang asli selalu dikurangkan
0x10000
dan komplemen 2's membiarkan overflow yang ditandatangani membungkusnya keint16_t
kisaran, versi ini memiliki eksplisitif
yang menghindari wrapover yang ditandatangani (yang tidak ditentukan ).Sekarang dalam praktiknya, hampir semua platform yang digunakan saat ini menggunakan representasi komplemen 2's. Bahkan, jika platform memiliki standar-standar
stdint.h
yang mendefinisikanint32_t
, itu harus menggunakan komplemen 2 untuk itu. Di mana pendekatan ini kadang-kadang berguna adalah dengan beberapa bahasa skrip yang tidak memiliki tipe data integer sama sekali - Anda dapat memodifikasi operasi yang ditunjukkan di atas untuk float dan itu akan memberikan hasil yang benar.sumber
int16_t
dan setiapintxx_t
dan varian yang tidak ditandatangani harus menggunakan representasi komplemen 2 tanpa bit padding. Ini akan mengambil arsitektur yang sengaja disalahgunakan untuk meng-host jenis ini dan menggunakan representasi lain untukint
, tapi saya kira DS9K dapat dikonfigurasi dengan cara ini.int
untuk menghindari kebingungan. Memang jika platform mendefinisikanint32_t
itu harus 2 komplemen.intN_t
menunjuk tipe integer bertanda dengan lebarN
, tanpa bit bantalan, dan representasi komplemen dua. Jadi,int8_t
menunjukkan tipe integer yang ditandatangani dengan lebar tepat 8 bit. Representasi lain masih didukung oleh standar, tetapi untuk tipe integer lainnya.(int)value
implementasikan perilaku yang ditentukan jika tipeint
hanya memiliki 16 bit. Saya khawatir Anda perlu menggunakan(long)value - 0x10000
, tetapi pada arsitektur pelengkap non 2, nilainya0x8000 - 0x10000
tidak dapat direpresentasikan sebagai 16-bitint
, sehingga masalahnya tetap ada.long
akan bekerja sama baiknya.Metode lain - menggunakan
union
:Dalam program:
first_byte
dansecond_byte
dapat ditukar sesuai dengan model endian kecil atau besar. Metode ini tidak lebih baik tetapi merupakan salah satu alternatif.sumber
byte[2]
danint16_t
ukuran yang sama, itu adalah satu atau yang lain dari dua kemungkinan pemesanan, bukan beberapa nilai tempat bitwise shuffled sewenang-wenang. Jadi, Anda setidaknya dapat mendeteksi pada waktu kompilasi berapa endianness implementasi.Operator aritmatika bergeser dan bitwise-atau dalam ekspresi
(uint16_t)data[0] | ((uint16_t)data[1] << 8)
tidak bekerja pada tipe yang lebih kecil dariint
, sehinggauint16_t
nilai - nilai tersebut dipromosikan keint
(atauunsigned
jikasizeof(uint16_t) == sizeof(int)
). Meski begitu, itu harus menghasilkan jawaban yang benar, karena hanya 2 byte yang lebih rendah yang mengandung nilai.Versi pedantically lain yang benar untuk konversi big-endian ke little-endian (dengan asumsi little-endian CPU) adalah:
memcpy
digunakan untuk menyalin representasiint16_t
dan itu adalah cara yang sesuai standar untuk melakukannya. Versi ini juga mengkompilasi menjadi 1 instruksimovbe
, lihat perakitan .sumber
__builtin_bswap16
adalah karena byte-swapping di ISO C tidak dapat diimplementasikan secara efisien.int16_t
menjadiuint16_t
terdefinisi dengan baik: nilai negatif dikonversi ke nilai lebih besar dariINT_MAX
, tetapi mengubah nilai-nilai ini kembali keuint16_t
perilaku implementasi yang didefinisikan: 6.3.1.3 Bilangan bulat bertanda dan tidak bertanda 1. Ketika nilai dengan tipe integer dikonversi ke tipe integer lain selain than_Bool, jika nilai dapat diwakili oleh tipe baru, itu tidak berubah. ... 3. Jika tidak, tipe baru ditandatangani dan nilainya tidak dapat diwakili di dalamnya; baik hasilnya adalah implementasi yang ditentukan atau sinyal implementasi yang ditetapkan dinaikkan.ntohs
/__builtin_bswap
dan|
/<<
pola: gcc.godbolt.org/z/rJ-j87Berikut adalah versi lain yang hanya bergantung pada perilaku portabel dan terdefinisi dengan baik (header
#include <endian.h>
bukan standar, kodenya):Versi little-endian mengkompilasi ke
movbe
instruksi tunggal denganclang
,gcc
versi kurang optimal, lihat perakitan .sumber
uint16_t
keint16_t
konversi, versi ini tidak memiliki konversi itu, jadi di sini Anda pergi.Saya ingin mengucapkan terima kasih kepada semua kontributor atas jawaban mereka. Inilah yang menjadi tujuan kerja kolektif:
uint8_t
,int16_t
danuint16_t
harus menggunakan representasi komplemen dua tanpa bit padding, sehingga bit sebenarnya dari representasi secara jelas dari 2 byte dalam array, dalam urutan yang ditentukan oleh nama fungsi.(unsigned)data[0] | ((unsigned)data[1] << 8)
(untuk versi endian kecil) mengkompilasi ke instruksi tunggal dan menghasilkan nilai 16-bit yang tidak ditandatangani.uint16_t
ke tipe yang ditandatanganiint16_t
memiliki perilaku implementasi yang ditetapkan jika nilainya tidak dalam kisaran tipe tujuan. Tidak ada ketentuan khusus yang dibuat untuk jenis yang perwakilannya didefinisikan secara tepat.INT_MAX
dan menghitung nilai yang ditandatangani dengan mengurangi0x10000
. Melakukan ini untuk semua nilai seperti yang disarankan oleh zwol dapat menghasilkan nilai di luar rentangint16_t
dengan perilaku yang didefinisikan implementasi yang sama.0x8000
bit secara eksplisit menyebabkan kompiler menghasilkan kode yang tidak efisien.memcpy
.Menggabungkan poin 2 dan 7, berikut ini adalah solusi portabel dan terdefinisi penuh yang mengkompilasi secara efisien ke satu instruksi dengan gcc dan dentang :
Perakitan 64-bit :
sumber
char
tipe yang bisa alias atau mengandung representasi objek dari jenis apa pun.uint16_t
bukan salah satu darichar
jenis, sehinggamemcpy
dariuint16_t
untukint16_t
tidak perilaku didefinisikan dengan baik. Standar hanya memerlukanchar[sizeof(T)] -> T > char[sizeof(T)]
konversi denganmemcpy
harus didefinisikan dengan baik.memcpy
ofuint16_t
toint16_t
adalah implementasi yang didefinisikan terbaik, tidak portabel, tidak terdefinisi dengan baik, persis seperti penugasan satu ke yang lain, dan Anda tidak dapat secara ajaib mengelaknya denganmemcpy
. Tidak masalah apakahuint16_t
menggunakan representasi komplemen dua atau tidak, atau bit padding ada atau tidak - itu bukan perilaku yang ditentukan atau diminta oleh standar C.r = u
kememcpy(&r, &u, sizeof u)
tapi yang terakhir ini tidak lebih baik dari yang pertama, bukan?