การ จำกัด การแปลงใน C ++ 0x มันเป็นแค่ตัวฉันเองหรือนี่ฟังดูเหมือนเป็นการเปลี่ยนแปลงอย่างสิ้นเชิง?


86

C ++ 0x จะสร้างโค้ดต่อไปนี้และโค้ดที่คล้ายกันมีรูปแบบไม่ถูกต้องเนื่องจากต้องมีการแปลงที่เรียกว่าการแปลง a doubleเป็นint.

int a[] = { 1.0 };

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


สำหรับการอ้างอิงโปรดดูที่ 8.5.4 / 6 ของ n3225

Conversion ที่แคบลงคือการแปลงโดยปริยาย

  • จากประเภททศนิยมไปยังประเภทจำนวนเต็มหรือ
  • จาก long double ไปเป็น double หรือ float หรือจาก double ไปจนถึง float ยกเว้นที่มาคือนิพจน์คงที่และมูลค่าที่แท้จริงหลังการแปลงอยู่ในช่วงของค่าที่สามารถแสดงได้ (แม้ว่าจะไม่สามารถแสดงได้อย่างแน่นอน) หรือ
  • จากประเภทจำนวนเต็มหรือประเภทการแจงนับที่ไม่ได้กำหนดขอบเขตเป็นประเภทจุด oating ยกเว้นที่มาเป็นนิพจน์คงที่และมูลค่าที่แท้จริงหลังจากการแปลงจะพอดีกับประเภทเป้าหมายและจะให้ค่าเดิมเมื่อแปลงกลับเป็นประเภทเดิมหรือ
  • จากประเภทจำนวนเต็มหรือประเภทการแจงนับที่ไม่ได้กำหนดขอบเขตไปเป็นประเภทจำนวนเต็มที่ไม่สามารถแสดงค่าทั้งหมดของประเภทเดิมได้ยกเว้นที่มาเป็นนิพจน์คงที่และมูลค่าจริงหลังจากการแปลงจะพอดีกับประเภทเป้าหมายและจะสร้างค่าดั้งเดิมเมื่อ แปลงกลับเป็นประเภทเดิม

1
สมมติว่าสิ่งนี้ใช้ได้สำหรับการเริ่มต้นของประเภท inbuilt เท่านั้นฉันไม่เห็นว่าจะเป็นอันตราย แน่นอนว่านี่อาจทำลายรหัสบางอย่าง แต่ควรแก้ไขได้ง่าย.
Johan Kotlinski

1
@ John Dibling: ไม่การเริ่มต้นไม่ได้เกิดขึ้นไม่ถูกต้องเมื่อสามารถแสดงค่าได้อย่างถูกต้องตามประเภทเป้าหมาย (และ0เป็นอยู่intแล้ว)
aschepler

2
@Nim: โปรดทราบว่านี่เป็นเพียงรูปแบบที่ไม่ถูกต้องภายในตัว{เริ่มต้นวงเล็บปีกกา}และการใช้งานแบบดั้งเดิมเพียงอย่างเดียวสำหรับอาร์เรย์และโครงสร้าง POD นอกจากนี้หากรหัสที่มีอยู่มีการร่ายอย่างชัดเจนว่าเป็นของใครรหัสนั้นจะไม่แตก
aschepler

4
@j_random_hacker ตามที่เอกสารการทำงานบอกว่าint a = 1.0;ยังใช้ได้
Johannes Schaub - litb

1
@litb: ขอบคุณ อันที่จริงฉันพบว่าเป็นเรื่องที่เข้าใจได้ แต่น่าผิดหวัง - IMHO จะดีกว่ามากหากต้องการไวยากรณ์ที่ชัดเจนสำหรับการแปลงที่แคบทั้งหมดตั้งแต่เริ่มต้น C ++
j_random_hacker

คำตอบ:


41

ฉันพบกับการเปลี่ยนแปลงครั้งนี้เมื่อฉันใช้ GCC คอมไพเลอร์พิมพ์ข้อผิดพลาดสำหรับโค้ดดังนี้:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}

ในฟังก์ชันvoid foo(const long long unsigned int&):

ข้อผิดพลาด: การ จำกัด การแปลง(((long long unsigned int)i) & 4294967295ull)จากภายในlong long unsigned intสู่unsigned intภายใน {}

ข้อผิดพลาด: การ จำกัด การแปลง(((long long unsigned int)i) >> 32)จากภายในlong long unsigned intสู่unsigned intภายใน {}

โชคดีที่ข้อความแสดงข้อผิดพลาดนั้นตรงไปตรงมาและการแก้ไขนั้นง่ายมาก:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
            static_cast<unsigned int>(i >> 32)};
}

รหัสอยู่ในไลบรารีภายนอกโดยมีเพียงสองรายการในไฟล์เดียว ฉันไม่คิดว่าการเปลี่ยนแปลงที่ทำลายจะส่งผลกระทบต่อโค้ดมากนัก สามเณรอาจจะได้รับการ สับสนว่า


9

ฉันจะประหลาดใจและผิดหวังในตัวเองที่รู้ว่ารหัส C ++ ใด ๆ ที่ฉันเขียนในช่วง 12 ปีที่ผ่านมามีปัญหาเช่นนี้ แต่คอมไพเลอร์ส่วนใหญ่จะพ่นคำเตือนเกี่ยวกับ "การ จำกัด " เวลาคอมไพล์ตลอดเว้นแต่ฉันจะพลาดอะไรไป

สิ่งเหล่านี้ทำให้ Conversion แคบลงด้วยหรือไม่

unsigned short b[] = { -1, INT_MAX };

ถ้าเป็นเช่นนั้นฉันคิดว่ามันอาจจะเกิดขึ้นบ่อยกว่าตัวอย่างประเภทลอยตัวถึงอินทิกรัล


1
ฉันไม่เข้าใจว่าทำไมคุณถึงพูดแบบนี้จึงไม่ใช่เรื่องแปลกที่จะพบในโค้ด ตรรกะระหว่างการใช้ -1 หรือ INT_MAX แทน USHRT_MAX คืออะไร? USHRT_MAX ไม่อยู่ในช่วงปลายปี 2010 ใช่หรือไม่

8

ตัวอย่างในทางปฏิบัติที่ฉันพบ:

float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};

ลิเทอรัลตัวเลขเป็นโดยปริยายdoubleซึ่งทำให้เกิดการส่งเสริม


1
เพื่อให้มันโดยการเขียนfloat 0.5f;)
underscore_d

2
@underscore_d ใช้ไม่ได้หากfloatเป็นพารามิเตอร์ typedef หรือ template (อย่างน้อยก็ไม่มีการสูญเสียความแม่นยำ) แต่ประเด็นก็คือโค้ดที่เขียนขึ้นทำงานร่วมกับความหมายที่ถูกต้องและกลายเป็นข้อผิดพลาดกับ C ++ 11 กล่าวคือคำจำกัดความของ "การเปลี่ยนแปลงที่ทำลาย"
Jed

7

ฉันจะไม่แปลกใจเลยถ้าใครบางคนโดนจับได้ว่า:

float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};

(ในการใช้งานของฉันสองรายการสุดท้ายไม่ให้ผลลัพธ์เหมือนกันเมื่อแปลงกลับเป็น int / long ดังนั้นจึงแคบลง)

ฉันจำไม่ได้ว่าเคยเขียนสิ่งนี้มาก่อน จะมีประโยชน์ก็ต่อเมื่อการประมาณขีด จำกัด นั้นมีประโยชน์สำหรับบางสิ่ง

อย่างน้อยก็ดูเหมือนจะคลุมเครือเช่นกัน:

void some_function(int val1, int val2) {
    float asfloat[] = {val1, val2};    // not in C++0x
    double asdouble[] = {val1, val2};  // not in C++0x
    int asint[] = {val1, val2};        // OK
    // now do something with the arrays
}

แต่มันไม่น่าเชื่ออย่างสิ้นเชิงเพราะถ้าฉันรู้ว่าฉันมีสองค่าอย่างแน่นอนทำไมต้องใส่ไว้ในอาร์เรย์แทนที่จะเป็นเพียงแค่float floatval1 = val1, floatval1 = val2;? อะไรคือแรงจูงใจทำไมจึงควรรวบรวม (และได้ผลหากการสูญเสียความแม่นยำอยู่ในความแม่นยำที่ยอมรับได้สำหรับโปรแกรม) ในขณะที่float asfloat[] = {val1, val2};ไม่ควร ไม่ว่าจะด้วยวิธีใดฉันกำลังเริ่มต้นการลอยตัวสองครั้งจากสอง ints มันเป็นเพียงแค่ว่าในกรณีหนึ่งการลอยทั้งสองเป็นสมาชิกของมวลรวม

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

char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?

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


9
"ถ้าฉันรู้ว่าฉันมีสองค่าทำไมต้องใส่ไว้ในอาร์เรย์" - เช่นเพราะ API อย่าง OpenGL ต้องการ
Georg Fritzsche

5

ลองเพิ่ม -Wno-narrowing ให้กับ CFLAGS ของคุณตัวอย่างเช่น:

CFLAGS += -std=c++0x -Wno-narrowing

หรือ CPPFLAGS ในกรณีของคอมไพเลอร์ C ++ (แน่นอนว่าขึ้นอยู่กับระบบการสร้างหรือ Makefile ของคุณ)
Mikolasan

4

การ จำกัด ข้อผิดพลาดในการแปลงจะโต้ตอบกับกฎการส่งเสริมการขายจำนวนเต็มโดยนัยไม่ดี

ฉันมีข้อผิดพลาดเกี่ยวกับรหัสที่ดูเหมือน

struct char_t {
    char a;
}

void function(char c, char d) {
    char_t a = { c+d };
}

ซึ่งก่อให้เกิดข้อผิดพลาดในการแปลงที่แคบลง (ซึ่งถูกต้องตามมาตรฐาน) เหตุผลก็คือcและdโดยปริยายได้รับการเลื่อนตำแหน่งintและผลลัพธ์intไม่ได้รับอนุญาตให้แคบลงกลับไปที่ char ในรายการ initializer

OTOH

void function(char c, char d) {
    char a = c+d;
}

แน่นอนว่ายังดีอยู่ (มิฉะนั้นนรกทั้งหมดจะหลุดออก) แต่ที่น่าแปลกใจคือ

template<char c, char d>
void function() {
    char_t a = { c+d };
}

ใช้ได้และคอมไพล์โดยไม่มีคำเตือนหากผลรวมของ c และ d น้อยกว่า CHAR_MAX ฉันยังคิดว่านี่เป็นข้อบกพร่องใน C ++ 11 แต่คนที่นั่นคิดเป็นอย่างอื่น - อาจเป็นเพราะมันไม่ง่ายที่จะแก้ไขโดยไม่ต้องกำจัดการแปลงจำนวนเต็มโดยปริยายอย่างใดอย่างหนึ่ง (ซึ่งเป็นความสัมพันธ์จากอดีตเมื่อผู้คนเขียนโค้ด ชอบchar a=b*c/dและคาดว่าจะใช้งานได้แม้ว่า (b * c)> CHAR_MAX) หรือ จำกัด ข้อผิดพลาดในการแปลงให้แคบลง (ซึ่งอาจเป็นสิ่งที่ดี)


ฉันพบสิ่งต่อไปนี้ซึ่งเป็นเรื่องไร้สาระที่น่ารำคาญจริงๆ: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- การทำให้ Conversion แคบลงภายใน {} จริงๆ? ดังนั้นตัวดำเนินการ & ยังแปลงอักขระที่ไม่ได้ลงชื่อเป็น int โดยปริยาย? ฉันไม่สนใจผลลัพธ์ก็ยังรับประกันได้ว่าจะเป็นถ่านที่ไม่ได้ลงชื่อ
Carlo Wood

" การแปลงจำนวนเต็มโดยปริยาย "?
ซอกแซก

2

มันเป็นการเปลี่ยนแปลงอย่างสิ้นเชิงเนื่องจากประสบการณ์ในชีวิตจริงด้วยคุณสมบัตินี้แสดงให้เห็นว่า gcc ได้กลายเป็นการเตือนจากข้อผิดพลาดในหลาย ๆ กรณีเนื่องจากความเจ็บปวดในชีวิตจริงด้วยการย้ายฐานรหัส C ++ 03 ไปยัง C ++ 11 ดูความคิดเห็นนี้ในรายงานข้อบกพร่องของ gcc :

มาตรฐานกำหนดเพียงว่า "การใช้งานที่สอดคล้องกันจะต้องออกข้อความวินิจฉัยอย่างน้อยหนึ่งข้อความ" ดังนั้นจึงอนุญาตให้รวบรวมโปรแกรมพร้อมคำเตือนได้ ดังที่แอนดรูว์กล่าวไว้ -Werror = การ จำกัด ให้คุณทำให้เกิดข้อผิดพลาดได้หากต้องการ

G ++ 4.6 ให้ข้อผิดพลาด แต่มันก็เปลี่ยนไปเตือนจงใจ 4.7เพราะหลายคน (รวมตัวเอง) พบว่าการกวดขันการแปลงที่หนึ่งที่พบมากที่สุดปัญหาเมื่อพยายามรวบรวมขนาดใหญ่ C ++ 03 codebases เป็น C โค้ดที่มีรูปแบบก่อนหน้านี้เช่น char c [] = {i, 0}; (โดยที่ฉันจะอยู่ในช่วงของ char เท่านั้น) ทำให้เกิดข้อผิดพลาดและต้องเปลี่ยนเป็น char c [] = {(char) i, 0}


1

ดูเหมือนว่า GCC-4.7 จะไม่ให้ข้อผิดพลาดในการ จำกัด Conversion อีกต่อไป แต่จะมีคำเตือนแทน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.