การเปรียบเทียบที่ลงชื่อ / ไม่ได้ลงนาม


87

ฉันพยายามทำความเข้าใจว่าเหตุใดรหัสต่อไปนี้จึงไม่ออกคำเตือนในสถานที่ที่ระบุ

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

ฉันคิดว่ามันเกี่ยวข้องกับการโปรโมตเบื้องหลัง แต่สองคนสุดท้ายดูเหมือนจะพูดเป็นอย่างอื่น

ในความคิดของฉันการ==เปรียบเทียบครั้งแรกเป็นเพียงการลงนาม / ไม่ได้ลงนามที่ไม่ตรงกันเท่าที่อื่น ๆ ?


3
gcc 4.4.2 พิมพ์คำเตือนเมื่อเรียกใช้ด้วย '-Wall'
bobah

นี่คือการคาดเดา แต่อาจเป็นการเพิ่มประสิทธิภาพการเปรียบเทียบทั้งหมดเนื่องจากรู้คำตอบในเวลารวบรวม
Null Set

2
อา! อีกครั้ง ความคิดเห็นของ Bobah: ฉันเปิดคำเตือนทั้งหมดและคำเตือนที่หายไปจะปรากฏขึ้น ฉันมีความเห็นว่าควรมีการตั้งค่าระดับการเตือนเช่นเดียวกับการเปรียบเทียบอื่น ๆ
Peter

1
@bobah: ผมเกลียดที่ GCC 4.4.2 การพิมพ์ที่เตือน (มีวิธีที่จะบอกได้ว่ามันจะพิมพ์เพียง แต่สำหรับความไม่เท่าเทียมกัน) เนื่องจากวิธีการทั้งหมดของสมรว่าสิ่งที่เตือนให้เลวร้ายยิ่ง การส่งเสริมการขายเริ่มต้นจะแปลงค่าสูงสุดที่เป็นไปได้ทั้ง -1 หรือ ~ 0 ให้เป็นค่าสูงสุดที่เป็นไปได้ของประเภทที่ไม่ได้ลงนามได้อย่างน่าเชื่อถือ แต่ถ้าคุณปิดเสียงคำเตือนโดยการแคสต์ด้วยตัวคุณเองคุณจะต้องทราบประเภทที่แน่นอน ดังนั้นหากคุณเปลี่ยนประเภท (ขยายให้เป็นแบบยาวที่ไม่ได้ลงนามแบบยาว) การเปรียบเทียบของคุณกับการเปลือย-1จะยังคงใช้งานได้ (แต่สิ่งเหล่านี้ให้คำเตือน) ในขณะที่การเปรียบเทียบของคุณกับ-1uหรือ(unsigned)-1ทั้งสองจะล้มเหลวอย่างน่าสังเวช
ม.ค. Hudec

ฉันไม่รู้ว่าทำไมคุณต้องมีคำเตือนและทำไมคอมไพเลอร์ไม่สามารถทำให้มันใช้งานได้ -1 เป็นลบจึงน้อยกว่าจำนวนที่ไม่ได้ลงชื่อใด ๆ Simples.
CashCow

คำตอบ:


96

เมื่อเปรียบเทียบการเซ็นชื่อกับไม่ได้ลงนามคอมไพลเลอร์จะแปลงค่าที่ลงนามเป็นไม่ได้ลงนาม -1 == (unsigned) -1เพื่อความเท่าเทียมกันนี้ไม่ได้เรื่อง สำหรับการเปรียบเทียบอื่น ๆ -1 > 2Uมันเป็นเรื่องสำคัญเช่นต่อไปนี้เป็นจริง:

แก้ไข: การอ้างอิง:

5/9: (นิพจน์)

ตัวดำเนินการไบนารีจำนวนมากที่คาดว่าตัวถูกดำเนินการของประเภทเลขคณิตหรือการแจงนับทำให้เกิดการแปลงและประเภทผลลัพธ์ในลักษณะเดียวกัน จุดประสงค์คือเพื่อให้ได้ประเภททั่วไปซึ่งเป็นประเภทของผลลัพธ์ด้วย รูปแบบนี้เรียกว่าการแปลงเลขคณิตตามปกติซึ่งกำหนดไว้ดังนี้:

  • ถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งเป็นประเภท long double อีกตัวจะถูกแปลงเป็น long double

  • มิฉะนั้นถ้าตัวถูกดำเนินการเป็นสองเท่าอีกตัวจะถูกแปลงเป็นสองเท่า

  • มิฉะนั้นถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งลอยอีกตัวจะถูกแปลงเป็นลอย

  • มิฉะนั้นการส่งเสริมแบบรวม (4.5) จะดำเนินการกับตัวถูกดำเนินการทั้งสอง 54)

  • จากนั้นถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งไม่ได้ลงชื่อยาวอีกตัวจะถูกแปลงเป็นแบบยาวที่ไม่ได้ลงนาม

  • มิฉะนั้นถ้าตัวถูกดำเนินการเป็น int ยาวและ int ที่ไม่ได้ลงนามอีกตัวหาก int ยาวสามารถแทนค่าทั้งหมดของ int ที่ไม่ได้ลงนาม int ที่ไม่ได้ลงนามจะถูกแปลงเป็น int แบบยาว มิฉะนั้นตัวถูกดำเนินการทั้งสองจะถูกแปลงเป็น int ยาวที่ไม่ได้ลงนาม

  • มิฉะนั้นถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งยาวอีกตัวจะถูกแปลงเป็น long

  • มิฉะนั้นถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งไม่ได้ลงนามอีกตัวจะถูกแปลงเป็นไม่ได้ลงนาม

4.7 / 2: (การแปลงอินทิกรัล)

หากประเภทปลายทางไม่ได้ลงนามค่าที่ได้คือจำนวนเต็มที่ไม่ได้ลงชื่อน้อยที่สุดที่สอดคล้องกับจำนวนเต็มต้นทาง (โมดูโล 2 nโดยที่ n คือจำนวนบิตที่ใช้แทนชนิดที่ไม่ได้ลงนาม) [หมายเหตุ: ในการแทนค่าสองส่วนการแปลงนี้เป็นไปตามแนวคิดและไม่มีการเปลี่ยนแปลงในรูปแบบบิต (หากไม่มีการตัดทอน) ]

แก้ไข 2: ระดับคำเตือน MSVC

สิ่งที่ได้รับการเตือนเกี่ยวกับระดับคำเตือนที่แตกต่างกันของ MSVC คือตัวเลือกที่นักพัฒนาสร้างขึ้น อย่างที่ฉันเห็นทางเลือกของพวกเขาเกี่ยวกับความเท่าเทียมกันที่ลงนาม / ไม่ได้ลงนามเทียบกับการเปรียบเทียบที่มากขึ้น / น้อยลงนั้นสมเหตุสมผลแน่นอนว่านี่เป็นเรื่องส่วนตัวทั้งหมด:

-1 == -1หมายความเช่นเดียวกับ-1 == (unsigned) -1- ฉันพบว่าผลลัพธ์ที่เข้าใจง่าย

-1 < 2 ไม่ได้หมายความว่าเช่นเดียวกับ-1 < (unsigned) 2- สิ่งนี้ใช้งานง่ายน้อยกว่าเมื่อมองแวบแรกและ IMO สมควรได้รับคำเตือน "ก่อนหน้านี้"


คุณจะแปลงเซ็นเป็นไม่ได้ลงนามได้อย่างไร? เวอร์ชันที่ไม่ได้ลงชื่อของค่าที่ลงนาม -1 คืออะไร (ลงนาม -1 = 1111 ในขณะที่ไม่ได้ลงนาม 15 = 1111 บิตอาจเท่ากัน แต่ไม่เท่ากันในเชิงตรรกะ) ฉันเข้าใจว่าถ้าคุณบังคับให้เกิดการแปลงนี้มันจะได้ผล แต่ทำไมคอมไพเลอร์ถึงทำเช่นนั้น มันไร้เหตุผล ยิ่งไปกว่านั้นตามที่ฉันแสดงความคิดเห็นข้างต้นเมื่อฉันเปิดคำเตือนคำเตือน == ที่หายไปปรากฏขึ้นซึ่งดูเหมือนว่าจะสำรองสิ่งที่ฉันพูด?
ปีเตอร์

1
ตามที่ 4.7 / 2 กล่าวการเซ็นชื่อเพื่อไม่ได้ลงนามหมายความว่าไม่มีการเปลี่ยนแปลงรูปแบบบิตสำหรับส่วนเติมเต็มของทั้งสอง เหตุใดคอมไพเลอร์จึงทำเช่นนี้จึงจำเป็นต้องมีตามมาตรฐาน C ++ ฉันเชื่อว่าเหตุผลที่อยู่เบื้องหลังคำเตือนของ VS ในระดับต่างๆคือโอกาสที่การแสดงออกโดยไม่ได้ตั้งใจ - และฉันเห็นด้วยกับพวกเขาว่าการเปรียบเทียบความเท่าเทียมกันของการลงนาม / ไม่ได้ลงนามนั้น "มีโอกาสน้อย" ที่จะเป็นปัญหามากกว่าการเปรียบเทียบความไม่เท่าเทียมกัน แน่นอนว่านี่เป็นเรื่องส่วนตัว - สิ่งเหล่านี้เป็นตัวเลือกที่นักพัฒนาคอมไพเลอร์ VC กำหนด
Erik

โอเคฉันคิดว่าฉันเกือบจะเข้าใจแล้ว วิธีที่ฉันอ่านนั่นคือคอมไพเลอร์คือ (แนวความคิด) ทำ: 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))' เนื่องจาก _int64 เป็นประเภทที่เล็กที่สุดที่สามารถแสดงทั้ง 0x7fffffff และ 0xffffffff ในเงื่อนไขที่ไม่ได้ลงนาม?
Peter

2
ที่จริงเมื่อเทียบกับ(unsigned)-1หรือ-1uมักจะเลวร้ายยิ่ง-1กว่าเมื่อเทียบกับ นั่นเป็นเพราะ(unsigned __int64)-1 == -1แต่(unsigned __int64)-1 != (unsigned)-1. ดังนั้นหากคอมไพเลอร์แจ้งเตือนคุณจะพยายามปิดเสียงโดยส่งไปยังไม่ได้ลงชื่อหรือใช้-1uและหากค่าเกิดขึ้นจริงเป็น 64 บิตหรือคุณเปลี่ยนเป็นค่าในภายหลังคุณจะทำลายโค้ดของคุณ! และจำไว้ว่าsize_tเป็น 64 บิตบนแพลตฟอร์ม 64 บิตเท่านั้นและการใช้ -1 สำหรับค่าที่ไม่ถูกต้องเป็นเรื่องปกติมาก
ม.ค. Hudec

1
บางที cpmpilers ไม่ควรทำเช่นนั้น หากเปรียบเทียบกับการเซ็นชื่อและไม่ได้ลงนามให้ตรวจสอบว่าค่าที่ลงชื่อเป็นลบหรือไม่ ถ้าเป็นเช่นนั้นรับประกันว่าจะน้อยกว่าที่ไม่ได้ลงนามโดยไม่คำนึงถึง
CashCow

33

เหตุใดคำเตือนที่ลงนาม / ไม่ได้ลงชื่อจึงมีความสำคัญและโปรแกรมเมอร์ต้องใส่ใจกับคำเตือนดังกล่าวแสดงให้เห็นโดยตัวอย่างต่อไปนี้

เดาผลลัพธ์ของรหัสนี้หรือไม่?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

เอาท์พุต:

i is greater than j

แปลกใจ? การสาธิตออนไลน์: http://www.ideone.com/5iCxY

Bottomline:ในการเปรียบเทียบถ้าตัวถูกดำเนินการตัวหนึ่งเป็นตัวถูกดำเนินunsignedการอีกตัวจะถูกแปลงโดยปริยายunsigned หากประเภทของมันถูกเซ็นชื่อ!


2
เขาพูดถูก! มันโง่ แต่เขาพูดถูก นี่คือ gotcha ที่สำคัญที่ฉันไม่เคยเจอมาก่อน เหตุใดจึงไม่แปลงค่าที่ไม่ได้ลงนามเป็นค่าที่มีลายเซ็น (ใหญ่กว่า)! หากคุณทำ "if (i <((int) j))" มันได้ผลตามที่คุณคาดหวัง แม้ว่า "if (i <((_int64) j))" จะสมเหตุสมผลกว่า (สมมติว่าคุณทำไม่ได้ แต่ _int64 นั้นมีขนาดเป็นสองเท่าของ int)
Peter

6
@Peter "ทำไมมันไม่แปลงค่า unsgiend เป็นค่าที่มีลายเซ็น (ใหญ่กว่า)" คำตอบนั้นง่ายมาก: อาจไม่มีค่าเซ็นที่ใหญ่กว่า ในเครื่อง 32 บิตในวันก่อน long long ทั้ง int และ long เป็น 32 บิตและไม่มีอะไรใหญ่ไปกว่านี้ เมื่อเปรียบเทียบการเซ็นชื่อและไม่ได้ลงนามคอมไพเลอร์ C ++ รุ่นแรกสุดจะแปลงทั้งคู่เป็นเซ็นชื่อ เพราะฉันลืมเหตุผลอะไรคณะกรรมการมาตรฐาน C จึงเปลี่ยนสิ่งนี้ ทางออกที่ดีที่สุดของคุณคือหลีกเลี่ยงการไม่ได้ลงนามให้มากที่สุด
James Kanze

5
@JamesKanze: ฉันสงสัยว่ามันต้องทำอะไรบางอย่างกับข้อเท็จจริงเช่นกันผลลัพธ์ของการโอเวอร์โฟลว์ที่ลงนามนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดในขณะที่ผลลัพธ์ของโอเวอร์โฟลว์ที่ไม่ได้ลงนามไม่ได้มีการกำหนดดังนั้นการแปลงค่าที่ลงนามเชิงลบเป็นไม่ได้ลงนามจึงถูกกำหนดในขณะที่การแปลงค่าที่ไม่ได้ลงชื่อจำนวนมากเป็นค่าลบที่มีการลงนาม ค่าไม่ได้
ม.ค. Hudec

2
@ เจมส์คอมไพเลอร์สามารถสร้างแอสเซมบลีที่จะใช้ความหมายที่ใช้งานง่ายมากขึ้นของการเปรียบเทียบนี้โดยไม่ต้องส่งไปยังประเภทที่ใหญ่กว่า i<0ในตัวอย่างนี้โดยเฉพาะอย่างยิ่งมันจะพอเพียงเพื่อให้ตรวจสอบก่อนว่า แล้วiมีขนาดเล็กกว่าjแน่นอน. หากiไม่น้อยกว่าศูนย์แล้วสามารถแปลงอย่างปลอดภัยไปยังไม่ได้ลงนามในการเปรียบเทียบกับì jแน่นอนว่าการเปรียบเทียบระหว่างเซ็นและไม่ได้ลงนามจะช้ากว่า แต่ผลลัพธ์ของมันจะถูกต้องมากกว่าในบางแง่
Sven

@ แม้ฉันเห็นด้วย มาตรฐานอาจต้องการให้การเปรียบเทียบทำงานกับค่าจริงทั้งหมดแทนที่จะแปลงเป็นหนึ่งในสองประเภท อย่างไรก็ตามสิ่งนี้ใช้ได้กับการเปรียบเทียบเท่านั้น ฉันสงสัยว่าคณะกรรมการไม่ต้องการกฎที่แตกต่างกันสำหรับการเปรียบเทียบและการดำเนินการอื่น ๆ (และไม่ต้องการโจมตีปัญหาในการระบุการเปรียบเทียบเมื่อไม่มีการเปรียบเทียบประเภทจริง)
James Kanze

4

ตัวดำเนินการ == ทำการเปรียบเทียบแบบบิต (โดยการหารอย่างง่ายเพื่อดูว่าเป็น 0 หรือไม่)

การเปรียบเทียบที่เล็ก / ใหญ่กว่านั้นขึ้นอยู่กับสัญลักษณ์ของตัวเลขมาก

ตัวอย่าง 4 บิต:

1111 = 15? หรือ -1?

ดังนั้นถ้าคุณมี 1111 <0001 ... มันคลุมเครือ ...

แต่ถ้าคุณมี 1111 == 1111 ... มันก็เหมือนกันทั้งๆที่คุณไม่ได้ตั้งใจ


ฉันเข้าใจสิ่งนี้ แต่ไม่ตอบคำถามของฉัน ตามที่คุณชี้ไว้ 1111! = 1111 หากสัญญาณไม่ตรงกัน คอมไพเลอร์รู้ว่ามีประเภทไม่ตรงกันเหตุใดจึงไม่เตือนเกี่ยวกับเรื่องนี้ (ประเด็นของฉันคือรหัสของฉันอาจมีความไม่ตรงกันหลายอย่างที่ฉันไม่ได้รับการเตือน)
ปีเตอร์

เป็นแบบที่ออกแบบมา การทดสอบความเท่าเทียมกันจะตรวจสอบความคล้ายคลึงกัน และก็มีความคล้ายคลึงกัน ฉันเห็นด้วยกับคุณว่ามันไม่ควรเป็นแบบนี้ คุณสามารถทำมาโครหรือสิ่งที่เกิน x == y ให้เป็น! ((x <y) || (x> y))
Yochai Timmer

1

ในระบบที่แสดงค่าโดยใช้ 2-complement (ตัวประมวลผลที่ทันสมัยที่สุด) จะมีค่าเท่ากันแม้ในรูปแบบไบนารี นี่อาจเป็นสาเหตุที่คอมไพเลอร์ไม่บ่นเกี่ยวกับa == bมาข

และฉันก็เป็นคอมไพเลอร์ที่แปลกไม่เตือนคุณบน== มา ((int) ข) ฉันคิดว่าควรให้คำเตือนการตัดทอนจำนวนเต็มหรืออะไรบางอย่าง


1
ปรัชญาของ C / C ++ คือคอมไพเลอร์เชื่อว่านักพัฒนารู้ว่าเขากำลังทำอะไรเมื่อแปลงระหว่างประเภทอย่างชัดเจน ดังนั้นจึงไม่มีคำเตือน (อย่างน้อยโดยค่าเริ่มต้น - ฉันเชื่อว่ามีคอมไพเลอร์ที่สร้างคำเตือนสำหรับสิ่งนี้หากตั้งค่าระดับการเตือนไว้สูงกว่าค่าเริ่มต้น)
PéterTörök

0

บรรทัดของรหัสที่เป็นปัญหาไม่สร้างคำเตือน C4018 เนื่องจาก Microsoft ใช้หมายเลขคำเตือนอื่น (เช่นC4389 ) เพื่อจัดการกับกรณีนั้นและ C4389 ไม่ได้เปิดใช้งานโดยค่าเริ่มต้น (เช่นที่ระดับ 3)

จากMicrosoft docsสำหรับ C4389:

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

คำตอบอื่น ๆ อธิบายได้ค่อนข้างดีว่าเหตุใด Microsoft จึงตัดสินใจทำกรณีพิเศษจากตัวดำเนินการความเท่าเทียมกัน แต่ฉันพบว่าคำตอบเหล่านั้นไม่เป็นประโยชน์อย่างยิ่งหากไม่กล่าวถึง C4389 หรือวิธีเปิดใช้งานใน Visual StudioStudio

ฉันควรพูดถึงว่าหากคุณกำลังจะเปิดใช้งาน C4389 คุณอาจลองเปิดใช้งาน C4388 ด้วย น่าเสียดายที่ไม่มีเอกสารอย่างเป็นทางการสำหรับ C4388 แต่ดูเหมือนว่าจะปรากฏขึ้นในนิพจน์ดังต่อไปนี้:

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.