Mengapa 0 <-0x80000000?

253

Saya memiliki program sederhana di bawah ini:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

Kondisinya if(bal < INT32_MIN )selalu benar. Bagaimana itu mungkin?

Ini berfungsi dengan baik jika saya mengubah makro ke:

#define INT32_MIN        (-2147483648L)

Adakah yang bisa menunjukkan masalah ini?

Jayesh Bhoi
sumber
3
Berapa harganya CHAR_BIT * sizeof(int)?
5gon12eder
1
Sudahkah Anda mencoba mencetak bal?
Ryan Fitzpatrick
10
IMHO, hal yang lebih menarik adalah bahwa itu benar hanya untuk -0x80000000, tetapi salah untuk -0x80000000L, -2147483648dan -2147483648L(gcc 4.1.2), jadi pertanyaannya adalah: mengapa int literal -0x80000000berbeda dari int literal -2147483648?
Andreas Fester
2
@Bathsheba Saya baru saja menjalankan program pada compiler online tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Jika Anda pernah memperhatikan bahwa (beberapa inkarnasi) <limits.h>didefinisikan INT_MINsebagai (-2147483647 - 1), sekarang Anda tahu mengapa.
zwol

Jawaban:

363

Ini cukup halus.

Setiap literal integer dalam program Anda memiliki tipe. Jenis yang dimilikinya diatur oleh tabel pada 6.4.4.1:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

Jika angka literal tidak dapat masuk ke dalam inttipe default , itu akan mencoba tipe yang lebih besar berikutnya seperti yang ditunjukkan pada tabel di atas. Jadi untuk literal bilangan bulat desimal biasa seperti:

  • Mencoba int
  • Jika tidak cocok, coba long
  • Jika tidak cocok, coba long long.

Hex literal berperilaku berbeda! Jika literal tidak dapat masuk ke dalam tipe bertanda suka int, ia akan terlebih dahulu mencoba unsigned intsebelum beralih ke mencoba tipe yang lebih besar. Lihat perbedaan pada tabel di atas.

Jadi pada sistem 32 bit, 0x80000000tipe literal Anda unsigned int.

Ini berarti bahwa Anda dapat menerapkan -operator unary pada literal tanpa menerapkan perilaku yang ditentukan implementasi, seperti yang Anda lakukan ketika meluap bilangan bulat yang ditandatangani. Sebaliknya, Anda akan mendapatkan nilai 0x80000000, nilai positif.

bal < INT32_MINmengaktifkan konversi aritmatika yang biasa dan hasil ekspresi 0x80000000dipromosikan dari unsigned intke long long. Nilai 0x80000000dipertahankan dan 0 kurang dari 0x80000000, karenanya hasilnya.

Ketika Anda mengganti literal dengan 2147483648LAnda menggunakan notasi desimal dan oleh karena itu kompiler tidak memilih unsigned int, tetapi mencoba untuk memasangnya di dalam a long. Sufiks L juga mengatakan bahwa Anda menginginkan long jika mungkin . Akhiran L sebenarnya memiliki aturan yang sama jika Anda terus membaca tabel yang disebutkan di 6.4.4.1: jika nomor tidak sesuai dengan yang diminta long, yang tidak dalam kasus 32 bit, kompiler akan memberi Anda tempat di long longmana akan cocok dengan baik.

Lundin
sumber
3
"... ganti literal dengan -2147483648L yang secara eksplisit kamu dapatkan, yang ditandatangani." Hmmm, Dalam 32-bit longsistem 2147483648L, tidak akan muat dalam long, sehingga menjadi long long, maka yang -diterapkan - atau jadi saya pikir.
chux - Reinstate Monica
2
@ASH Karena jumlah maksimum yang dimiliki int adalah 0x7FFFFFFF. Coba sendiri:#include <limits.h> printf("%X\n", INT_MAX);
Lundin
5
@ASH Jangan bingung representasi heksadesimal dari integer literal dalam kode sumber dengan representasi biner yang mendasari dari nomor yang ditandatangani. Literal 0x7FFFFFFFketika ditulis dalam kode sumber selalu angka positif, tetapi intvariabel Anda tentu saja dapat berisi angka biner mentah hingga nilai 0xFFFFFFFF.
Lundin
2
@ASH ìnt n = 0x80000000memaksa konversi dari literal tanpa tanda tangan ke tipe yang ditandatangani. Apa yang akan terjadi tergantung pada kompiler Anda - itu adalah perilaku yang ditentukan implementasi. Dalam hal ini ia memilih untuk menunjukkan keseluruhan literal ke dalam int, menimpa bit tanda. Pada sistem lain mungkin tidak mungkin untuk mewakili tipe dan Anda menjalankan perilaku yang tidak terdefinisi - program mungkin macet. Anda akan mendapatkan perilaku yang sama jika Anda melakukannya int n=2147483648;tidak terkait dengan notasi heksa sama sekali.
Lundin
3
Penjelasan tentang bagaimana unary -diterapkan pada bilangan bulat yang tidak ditandatangani dapat diperluas sedikit. Saya selalu berasumsi (walaupun untungnya tidak pernah bergantung pada asumsi) bahwa nilai yang tidak ditandatangani akan "dipromosikan" ke nilai yang ditandatangani, atau mungkin hasilnya tidak akan ditentukan. (Jujur, itu harus menjadi kesalahan kompilasi; apa - 3uartinya?)
Kyle Strand
27

0x80000000adalah unsignedliteral dengan nilai 2147483648.

Menerapkan minus unary pada ini masih memberi Anda jenis unsigned dengan nilai bukan nol. (Faktanya, untuk nilai yang bukan nol x, nilai yang Anda dapatkan adalah UINT_MAX - x + 1.)

Batsyeba
sumber
23

Literal integer ini 0x80000000memiliki tipe unsigned int.

Menurut Standar C (6.4.4.1 Konstanta bilangan bulat)

5 Jenis konstanta integer adalah yang pertama dari daftar yang sesuai di mana nilainya dapat diwakili.

Dan konstanta integer ini dapat diwakili oleh tipe unsigned int.

Jadi ungkapan ini

-0x80000000memiliki unsigned inttipe yang sama . Selain itu memiliki nilai yang sama 0x80000000dalam representasi komplemen keduanya yang menghitung dengan cara berikut

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

Ini memiliki efek samping jika menulis misalnya

int x = INT_MIN;
x = abs( x );

Hasilnya akan kembali INT_MIN.

Demikian di dalam kondisi ini

bal < INT32_MIN

ada dibandingkan 0dengan nilai unsigned yang0x80000000 dikonversi ke tipe long int menurut aturan konversi aritmatika yang biasa.

Jelaslah bahwa 0 lebih kecil dari 0x80000000.

Vlad dari Moskow
sumber
12

Konstanta numerik 0x80000000adalah tipe unsigned int. Jika kita mengambil -0x80000000dan melakukan 2s matematika pujian, kita dapatkan ini:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

Jadi -0x80000000 == 0x80000000. Dan membandingkan (0 < 0x80000000)(karena 0x80000000tidak ditandatangani) adalah benar.

dbush
sumber
Ini mengandaikan 32-bit ints. Meskipun itu pilihan yang sangat umum, dalam implementasi yang diberikan intmungkin lebih sempit atau lebih luas. Namun, ini adalah analisis yang tepat untuk kasus itu.
John Bollinger
Ini tidak relevan dengan kode OP, -0x80000000adalah aritmatika yang tidak ditandatangani. ~0x800000000adalah kode yang berbeda.
MM
Sepertinya ini jawaban terbaik dan benar bagi saya. @ MM dia menjelaskan bagaimana cara mengambil dua pasangan pelengkap. Jawaban ini secara khusus membahas apa yang dilakukan tanda negatif terhadap nomor tersebut.
Octopus
@Octopus tanda negatifnya tidak menerapkan pelengkap 2 untuk angka (!) Meskipun ini tampak jelas, itu tidak menggambarkan apa yang terjadi dalam kode -0x80000000! Bahkan komplemen 2 tidak relevan dengan pertanyaan ini sepenuhnya.
MM
12

Suatu titik kebingungan muncul ketika berpikir bahwa itu -adalah bagian dari konstanta numerik.

Dalam kode di bawah 0x80000000ini adalah konstanta numerik. Jenisnya hanya menentukan itu. Ini -diterapkan sesudahnya dan tidak mengubah jenisnya .

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Konstanta numerik mentah tanpa hiasan adalah positif.

Jika desimal, maka jenis ditugaskan adalah jenis pertama yang akan terus: int, long, long long.

Jika konstan adalah oktal atau heksadesimal, hal itu akan jenis pertama yang memegang itu: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, pada sistem OP mendapatkan jenis unsignedatau unsigned long. Apa pun itu, ini adalah tipe yang tidak ditandatangani.

-0x80000000juga beberapa nilai non-nol dan menjadi beberapa tipe yang tidak ditandatangani, ini lebih besar dari 0. Ketika kode membandingkannya dengan a long long, nilainya tidak berubah pada 2 sisi perbandingan, begitu 0 < INT32_MINjuga benar.


Definisi alternatif menghindari perilaku aneh ini

#define INT32_MIN        (-2147483647 - 1)

Mari kita berjalan di dunia fantasi untuk sementara di mana intdan unsigned48-bit.

Kemudian 0x80000000cocok intdan begitu juga tipenya int. -0x80000000kemudian merupakan angka negatif dan hasil cetaknya berbeda.

[Kembali ke kata sebenarnya]

Karena 0x80000000cocok dengan beberapa jenis yang tidak ditandatangani sebelum jenis yang ditandatangani karena hanya lebih besar dari yang some_signed_MAXada di some_unsigned_MAXdalamnya, itu adalah beberapa jenis yang tidak ditandatangani.

chux - Pasang kembali Monica
sumber
8

C memiliki aturan bahwa literer integer mungkin signedatau unsignedtergantung pada apakah itu cocok signedatau tidak unsigned(promosi integer). Pada 32mesin-bit, literalnya 0x80000000adalah unsigned. Komplemen 2 -0x80000000ada 0x80000000 pada mesin 32-bit. Oleh karena itu, perbandingannya bal < INT32_MINadalah antara signeddan unsignedsebelum perbandingan sesuai aturan C unsigned intakan dikonversi menjadi long long.

C11: 6.3.1.8/1:

[...] 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.

Karena itu, bal < INT32_MINselalu true.

haccks
sumber