Masuk ke konversi yang tidak ditandatangani dalam C - apakah selalu aman?

135

Misalkan saya memiliki kode C berikut.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Konversi tersirat apa yang terjadi di sini, dan apakah kode ini aman untuk semua nilai udan i? (Aman, dalam arti bahwa meskipun hasil dalam contoh ini akan meluap ke beberapa angka positif yang sangat besar, saya bisa melemparkannya kembali ke int dan mendapatkan hasil yang sebenarnya.)

cwick
sumber

Jawaban:

223

Jawaban singkat

Anda iakan dikonversi ke integer yang tidak ditandatangani dengan menambahkan UINT_MAX + 1, maka penambahan akan dilakukan dengan nilai yang tidak ditandatangani, menghasilkan nilai yang besar result(tergantung pada nilai-nilai udan i).

Jawaban panjang

Menurut Standar C99:

6.3.1.8 Konversi aritmatika biasa

  1. Jika kedua operan memiliki tipe yang sama, maka tidak diperlukan konversi lebih lanjut.
  2. Jika tidak, jika kedua operan memiliki tipe integer yang ditandatangani atau keduanya memiliki tipe integer yang tidak ditandatangani, operan dengan jenis peringkat konversi integer yang lebih rendah dikonversi ke jenis operan dengan peringkat yang lebih besar.
  3. Sebaliknya, jika operan yang memiliki tipe bilangan bulat bertanda memiliki peringkat lebih besar atau sama dengan pangkat dari jenis operan lain, maka operan dengan tipe bilangan bulat bertanda dikonversi ke jenis operan dengan jenis bilangan bulat bertanda.
  4. Jika tidak, jika jenis operan dengan tipe integer yang ditandatangani dapat mewakili semua nilai dari tipe operan dengan tipe integer yang tidak ditandatangani, maka operand dengan tipe integer yang tidak ditandatangani dikonversi ke jenis operan dengan tipe integer yang ditandatangani.
  5. Jika tidak, kedua operan dikonversikan ke tipe integer yang tidak ditandatangani yang sesuai dengan tipe operan dengan tipe integer yang ditandatangani.

Dalam kasus Anda, kami memiliki satu unsigned int ( u) dan int int ( i). Mengacu pada (3) di atas, karena kedua operan memiliki peringkat yang sama, Anda iharus dikonversi ke integer yang tidak ditandatangani.

6.3.1.3 Bilangan bulat yang ditandatangani dan tidak ditandatangani

  1. Ketika nilai dengan tipe integer dikonversi ke tipe integer lain selain _Bool, jika nilainya dapat diwakili oleh tipe baru, itu tidak berubah.
  2. Jika tidak, jika tipe baru tidak ditandatangani, nilainya dikonversi dengan berulang kali menambahkan atau mengurangi satu lebih dari nilai maksimum yang dapat direpresentasikan dalam tipe baru hingga nilainya berada dalam kisaran tipe baru.
  3. Jika tidak, tipe baru ditandatangani dan nilainya tidak dapat diwakili di dalamnya; baik hasilnya adalah implementasi yang ditentukan atau sinyal yang ditentukan oleh implementasi dinaikkan.

Sekarang kita perlu merujuk ke (2) di atas. Anda iakan dikonversi ke nilai yang tidak ditandatangani dengan menambahkan UINT_MAX + 1. Jadi hasilnya akan tergantung pada bagaimana UINT_MAXdidefinisikan pada implementasi Anda. Itu akan besar, tetapi tidak akan meluap, karena:

6.2.5 (9)

Suatu perhitungan yang melibatkan operan yang tidak ditandatangani tidak pernah dapat meluap, karena hasil yang tidak dapat diwakili oleh tipe integer yang tidak ditandatangani berkurang modulo angka yang satu lebih besar dari nilai terbesar yang dapat diwakili oleh tipe yang dihasilkan.

Bonus: Semi-WTF Konversi Aritmatika

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Anda dapat menggunakan tautan ini untuk mencoba ini secara online: https://repl.it/repls/QuickWhimsicalBytes

Bonus: Efek Samping Konversi Aritmatika

Aturan konversi aritmatika dapat digunakan untuk mendapatkan nilai UINT_MAXdengan menginisialisasi nilai yang tidak ditandatangani -1, yaitu:

unsigned int umax = -1; // umax set to UINT_MAX

Ini dijamin portabel terlepas dari representasi nomor yang ditandatangani sistem karena aturan konversi yang dijelaskan di atas. Lihat pertanyaan SO ini untuk informasi lebih lanjut: Apakah aman menggunakan -1 untuk mengatur semua bit menjadi true?

Ozgur Ozcitak
sumber
Saya tidak mengerti mengapa itu tidak bisa hanya melakukan nilai absolut dan kemudian memperlakukannya sebagai tidak ditandatangani, sama seperti dengan angka positif?
Jose Salvatierra
7
@ D.Singh, bisakah Anda menunjuk ke bagian yang salah dalam jawaban?
Shmil The Cat
Untuk mengonversikan yang ditandatangani menjadi unsigned, kami menambahkan nilai maksimum dari nilai unsigned (UINT_MAX +1). Demikian pula apa cara mudah untuk mengkonversi dari tidak ditandatangani menjadi ditandatangani? Apakah kita perlu mengurangi angka yang diberikan dari nilai maks (256 untuk char yang tidak ditandatangani)? Sebagai contoh: 140 ketika dikonversi ke nomor yang ditandatangani menjadi -116. Tetapi 20 menjadi 20 itu sendiri. Jadi ada trik mudah di sini?
Jon Wheelock
@JonWheelock lihat: stackoverflow.com/questions/8317295/…
Ozgur Ozcitak
24

Konversi dari yang ditandatangani ke yang tidak ditandatangani tidak serta merta hanya menyalin atau menafsirkan kembali representasi dari nilai yang ditandatangani. Mengutip standar C (C99 6.3.1.3):

Ketika nilai dengan tipe integer dikonversi ke tipe integer lain selain _Bool, jika nilainya dapat diwakili oleh tipe baru, itu tidak berubah.

Jika tidak, jika tipe baru tidak ditandatangani, nilainya dikonversi dengan berulang kali menambahkan atau mengurangi satu lebih dari nilai maksimum yang dapat direpresentasikan dalam tipe baru hingga nilainya berada dalam kisaran tipe baru.

Jika tidak, tipe baru ditandatangani dan nilainya tidak dapat diwakili di dalamnya; baik hasilnya adalah implementasi yang ditentukan atau sinyal yang ditentukan oleh implementasi dinaikkan.

Untuk representasi komplemen keduanya yang hampir universal akhir-akhir ini, aturannya sesuai dengan menafsirkan ulang bit. Tetapi untuk representasi lain (sign-and-magnitude atau komplemen satu), implementasi C masih harus mengatur untuk hasil yang sama, yang berarti bahwa konversi tidak bisa hanya menyalin bit. Misalnya, (tidak ditandatangani) -1 == UINT_MAX, terlepas dari representasi.

Secara umum, konversi dalam C didefinisikan untuk beroperasi pada nilai, bukan pada representasi.

Untuk menjawab pertanyaan awal:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Nilai i dikonversi ke int unsigned, menghasilkan UINT_MAX + 1 - 5678. Nilai ini kemudian ditambahkan ke nilai unsigned 1234, menghasilkan UINT_MAX + 1 - 4444.

(Tidak seperti overflow yang tidak ditandai, overflow yang ditandatangani memunculkan perilaku yang tidak terdefinisi. Wraparound biasa terjadi, tetapi tidak dijamin oleh standar C - dan optimisasi kompiler dapat mendatangkan malapetaka pada kode yang membuat asumsi yang tidak beralasan.)


sumber
5

Mengacu pada Alkitab :

  • Operasi penjumlahan Anda menyebabkan int dikonversi menjadi int yang tidak ditandatangani.
  • Dengan asumsi representasi komplemen dua dan tipe berukuran sama, pola bit tidak berubah.
  • Konversi dari int yang tidak ditandatangani ke int yang ditandatangani bergantung pada implementasi. (Tapi mungkin ini bekerja seperti yang Anda harapkan pada sebagian besar platform saat ini.)
  • Aturannya sedikit lebih rumit dalam hal menggabungkan ukuran yang ditandatangani dan tidak ditandatangani.
smh
sumber
3

Ketika satu unsigned dan satu variabel yang ditandatangani ditambahkan (atau operasi biner) keduanya secara implisit dikonversi menjadi unsigned, yang dalam hal ini akan menghasilkan hasil yang sangat besar.

Jadi aman dalam arti bahwa hasilnya mungkin besar dan salah, tetapi tidak akan pernah crash.

Mats Fredriksson
sumber
Tidak benar. 6.3.1.8 Konversi aritmatika biasa Jika Anda menjumlahkan int dan dan char yang tidak ditandatangani, konversi terakhir dikonversi ke int. Jika Anda menjumlahkan dua char yang tidak ditandatangani, mereka akan dikonversi menjadi int.
2501
3

Saat mengonversi dari masuk ke ditandatangani, ada dua kemungkinan. Angka yang awalnya positif tetap (atau ditafsirkan sebagai) nilai yang sama. Angka yang awalnya negatif sekarang akan ditafsirkan sebagai angka positif yang lebih besar.

Tim Ring
sumber
1

Seperti yang telah dijawab sebelumnya, Anda dapat melakukan bolak-balik antara yang ditandatangani dan yang tidak ditandatangani tanpa masalah. Kasus batas untuk bilangan bulat yang ditandatangani adalah -1 (0xFFFFFFFF). Coba tambahkan dan kurangi dari itu dan Anda akan menemukan bahwa Anda dapat membalas dan membuatnya benar.

Namun, jika Anda akan melakukan bolak-balik, saya akan sangat menyarankan penamaan variabel Anda sehingga jelas apa jenisnya, misalnya:

int iValue, iResult;
unsigned int uValue, uResult;

Terlalu mudah untuk teralihkan oleh masalah yang lebih penting dan melupakan variabel mana yang jenisnya jika dinamai tanpa petunjuk. Anda tidak ingin melakukan cast ke unsigned dan kemudian menggunakannya sebagai indeks array.

Taylor Price
sumber
0

Konversi tersirat apa yang terjadi di sini,

saya akan dikonversi ke integer yang tidak ditandatangani.

dan apakah kode ini aman untuk semua nilai u dan i?

Aman dalam arti didefinisikan dengan baik ya (lihat https://stackoverflow.com/a/50632/5083516 ).

Aturan ditulis dalam biasanya sulit untuk membaca standar-berbicara tetapi pada dasarnya representasi apa pun yang digunakan dalam bilangan bulat yang ditandatangani bilangan bulat bertanda akan berisi representasi komplemen 2 dari angka.

Penambahan, pengurangan dan penggandaan akan bekerja dengan benar pada angka-angka ini sehingga menghasilkan bilangan bulat tak bertanda yang berisi dua nomor komplemen yang mewakili "hasil nyata".

pembagian dan casting untuk tipe integer besar yang tidak ditandatangani akan memiliki hasil yang terdefinisi dengan baik tetapi hasil tersebut tidak akan menjadi representasi pelengkap 2 dari "hasil nyata".

(Aman, dalam arti bahwa meskipun hasil dalam contoh ini akan meluap ke beberapa angka positif yang sangat besar, saya bisa melemparkannya kembali ke int dan mendapatkan hasil yang sebenarnya.)

Sementara konversi dari masuk ke unsigned ditentukan oleh standar, sebaliknya adalah implement-defined baik gcc dan msvc mendefinisikan konversi sedemikian rupa sehingga Anda akan mendapatkan "hasil nyata" ketika mengonversi angka komplemen 2 yang disimpan dalam integer yang tidak ditandatangani kembali ke integer yang ditandatangani menjadi integer yang ditandatangani . Saya berharap Anda hanya akan menemukan perilaku lain pada sistem tidak jelas yang tidak menggunakan komplemen 2's untuk bilangan bulat yang ditandatangani.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

plugwash
sumber
-17

Kelimpahan Jawaban Mengerikan

Ozgur Ozcitak

Ketika Anda memilih dari masuk ke yang tidak ditandatangani (dan sebaliknya) representasi internal dari nomor tidak berubah. Apa yang berubah adalah bagaimana kompiler menginterpretasikan bit tanda.

Ini sepenuhnya salah.

Mats Fredriksson

Ketika satu unsigned dan satu variabel yang ditandatangani ditambahkan (atau operasi biner) keduanya secara implisit dikonversi menjadi unsigned, yang dalam hal ini akan menghasilkan hasil yang sangat besar.

Ini juga salah. Int yang tidak ditandatangani dapat dipromosikan ke int jika mereka memiliki presisi yang sama karena bit padding dalam tipe yang tidak ditandatangani.

smh

Operasi penjumlahan Anda menyebabkan int dikonversi menjadi int yang tidak ditandatangani.

Salah. Mungkin ya dan mungkin juga tidak.

Konversi dari int yang tidak ditandatangani ke int yang ditandatangani bergantung pada implementasi. (Tapi mungkin ini bekerja seperti yang Anda harapkan pada sebagian besar platform saat ini.)

Salah. Entah itu perilaku yang tidak terdefinisi jika menyebabkan overflow atau nilainya dipertahankan.

Anonim

Nilai i dikonversi ke int unsigned ...

Salah. Tergantung pada ketepatan int relatif terhadap int unsigned.

Taylor Price

Seperti yang telah dijawab sebelumnya, Anda dapat melakukan bolak-balik antara yang ditandatangani dan yang tidak ditandatangani tanpa masalah.

Salah. Mencoba menyimpan nilai di luar rentang bilangan bulat yang ditandatangani menghasilkan perilaku yang tidak terdefinisi.

Sekarang saya akhirnya bisa menjawab pertanyaan itu.

Jika presisi int sama dengan int unsigned, u akan dipromosikan ke int yang ditandatangani dan Anda akan mendapatkan nilai -4444 dari ekspresi (u + i). Sekarang, jika Anda dan saya memiliki nilai-nilai lain, Anda mungkin mendapatkan perilaku overflow dan undefined tetapi dengan angka-angka yang tepat Anda akan mendapatkan -4444 [1] . Nilai ini akan memiliki tipe int. Tetapi Anda mencoba untuk menyimpan nilai itu ke int yang tidak ditandatangani sehingga kemudian akan dilemparkan ke int yang tidak ditandatangani dan nilai yang hasilnya akan berakhir adalah (UINT_MAX + 1) - 4444.

Jika presisi dari unsigned int lebih besar daripada int, int yang ditandatangani akan dipromosikan ke int unsigned yang menghasilkan nilai (UINT_MAX + 1) - 5678 yang akan ditambahkan ke int unsigned 1234 yang lain. Haruskah Anda dan saya memiliki nilai-nilai lain, yang membuat ekspresi berada di luar rentang {0..UINT_MAX} nilai (UINT_MAX + 1) akan ditambahkan atau dikurangi hingga hasilnya TIDAK termasuk dalam rentang {0..UINT_MAX) dan tidak ada perilaku tidak terdefinisi yang akan terjadi .

Apa itu presisi?

Integer memiliki bit padding, bit tanda, dan bit nilai. Bilangan bulat yang tidak ditandai tidak memiliki sedikit tanda. Selanjutnya, unsigned char dijamin tidak memiliki bit padding. Jumlah bit nilai yang dimiliki integer adalah seberapa banyak presisi yang dimilikinya.

[Gotchas]

Ukuran makro dari makro saja tidak dapat digunakan untuk menentukan presisi dari bilangan bulat jika bit bantalan hadir. Dan ukuran byte tidak harus berupa octet (delapan bit) seperti yang didefinisikan oleh C99.

[1] Overflow dapat terjadi pada salah satu dari dua titik. Baik sebelum penambahan (selama promosi) - ketika Anda memiliki int unsigned yang terlalu besar untuk masuk ke dalam int. Overflow juga dapat terjadi setelah penambahan bahkan jika int unsigned berada dalam kisaran int, setelah penambahan hasilnya mungkin masih meluap.

Elite Mx
sumber
6
"Int yang tidak ditandatangani dapat dipromosikan ke int". Tidak benar. Tidak ada promosi integer yang terjadi karena tipenya sudah peringkat> = int. 6.3.1.1: "Peringkat dari tipe integer yang tidak ditandatangani harus sama dengan peringkat dari tipe integer yang ditandatangani, jika ada." dan 6.3.1.8: "Jika tidak, jika operan yang memiliki tipe bilangan bulat bertanda memiliki peringkat lebih besar atau sama dengan pangkat dari jenis operan lain, maka operan dengan tipe bilangan bulat bertanda dikonversi ke jenis operan dengan bilangan bulat unsigned Tipe." keduanya menjamin bahwa intdikonversi menjadi unsigned intketika konversi aritmatika yang biasa berlaku.
CB Bailey
1
6.3.1.8 Terjadi hanya setelah promosi bilangan bulat. Paragraf pembuka mengatakan "Jika tidak, promosi bilangan bulat dilakukan pada kedua operan. KEMUDIAN aturan berikut ini diterapkan pada operan yang dipromosikan". Jadi bacalah aturan promosi 6.3.1.1 ... "Objek atau ekspresi dengan tipe integer yang peringkat konversi integernya kurang dari atau EQUAL ke peringkat int dan unsigned int" dan "Jika sebuah int dapat mewakili semua nilai dari tipe asli, nilainya dikonversi menjadi int ".
Elite Mx
1
6.3.1.1 Promosi integer digunakan untuk mengonversi beberapa tipe integer yang bukan intatau unsigned intke salah satu tipe di mana sesuatu tipe unsigned intatau intdiharapkan. "Atau sama" ditambahkan dalam TC2 untuk memungkinkan jenis peringkat peringkat yang sama dengan intatau unsigned intdikonversi ke salah satu dari jenis tersebut. Itu tidak pernah dimaksudkan bahwa promosi yang dijelaskan akan mengkonversi antara unsigned intdan int. Penentuan tipe umum antara unsigned intdan intmasih diatur oleh 6.3.1.8, bahkan setelah TC2.
CB Bailey
19
Posting jawaban yang salah sambil mengkritik jawaban salah orang lain tidak terdengar seperti strategi yang baik untuk mendapatkan pekerjaan ... ;-)
R .. GitHub BERHENTI MEMBANTU ICE
6
Saya tidak memilih untuk menghapus karena tingkat kesalahan ini dikombinasikan dengan kesombongan terlalu menghibur
MM