Thu hẹp chuyển đổi trong C ++ 0x. Có phải chỉ tôi không, hay điều này giống như một sự thay đổi đột ngột?


85

C ++ 0x sẽ làm cho đoạn mã sau và mã tương tự trở nên sai lệch, bởi vì nó yêu cầu cái gọi là chuyển đổi thu hẹp từ a doublethành a int.

int a[] = { 1.0 };

Tôi tự hỏi liệu kiểu khởi tạo này có được sử dụng nhiều trong mã thế giới thực hay không. Có bao nhiêu mã sẽ bị hỏng bởi thay đổi này? Có nhiều nỗ lực để sửa lỗi này trong mã của bạn không, nếu mã của bạn bị ảnh hưởng?


Để tham khảo, hãy xem 8.5.4 / 6 của n3225

Chuyển đổi thu hẹp là một chuyển đổi ngầm định

  • từ kiểu dấu phẩy động sang kiểu số nguyên hoặc
  • từ kép dài sang kép hoặc float, hoặc từ kép sang float, ngoại trừ trường hợp nguồn là biểu thức hằng số và giá trị thực sau khi chuyển đổi nằm trong phạm vi giá trị có thể được biểu diễn (ngay cả khi nó không thể được biểu diễn chính xác) hoặc
  • từ kiểu số nguyên hoặc kiểu liệt kê chưa được ghi vào kiểu điểm fl oating-point, ngoại trừ trường hợp nguồn là một biểu thức hằng số và giá trị thực sau khi chuyển đổi sẽ phù hợp với kiểu đích và sẽ tạo ra giá trị ban đầu khi được chuyển đổi trở lại kiểu ban đầu, hoặc
  • từ kiểu số nguyên hoặc kiểu liệt kê không được ghi vào kiểu số nguyên không thể đại diện cho tất cả các giá trị của kiểu ban đầu, ngoại trừ trường hợp nguồn là biểu thức hằng số và giá trị thực sau khi chuyển đổi sẽ phù hợp với kiểu đích và sẽ tạo ra giá trị ban đầu khi chuyển đổi trở lại loại ban đầu.

1
Giả sử điều này chỉ hợp lệ để khởi tạo các loại có sẵn, tôi không thể hiểu điều này sẽ gây hại như thế nào. Chắc chắn, điều này có thể phá vỡ một số mã. Nhưng nên dễ sửa chữa.
Johan Kotlinski

1
@John Dibling: Không, quá trình khởi tạo không phải là sai khi giá trị có thể được thể hiện chính xác bằng loại đích. (Và dù sao cũng 0đã là một int.)
aschepler

2
@Nim: Lưu ý rằng điều này chỉ được tạo ra trong các bộ {khởi tạo dấu ngoặc nhọn }và cách sử dụng kế thừa duy nhất của chúng là cho các mảng và cấu trúc POD. Ngoài ra, nếu mã hiện tại có phôi rõ ràng nơi chúng thuộc về, nó sẽ không bị hỏng.
aschepler

4
@j_random_hacker như giấy làm việc cho biết, int a = 1.0;vẫn còn hiệu lực.
Johannes Schaub - litb 14/12/10

1
@litb: Cảm ơn. Trên thực tế, tôi thấy điều đó dễ hiểu nhưng đáng thất vọng - IMHO sẽ tốt hơn nhiều nếu yêu cầu cú pháp rõ ràng cho tất cả các chuyển đổi thu hẹp ngay từ đầu C ++.
j_random_hacker

Câu trả lời:


41

Tôi đã gặp phải sự thay đổi đột phá này khi sử dụng GCC. Trình biên dịch đã in lỗi cho mã như sau:

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

Trong chức năng void foo(const long long unsigned int&):

lỗi: thu hẹp chuyển đổi (((long long unsigned int)i) & 4294967295ull)từ long long unsigned intđến unsigned intbên trong {}

lỗi: thu hẹp chuyển đổi (((long long unsigned int)i) >> 32)từ long long unsigned intđến unsigned intbên trong {}

May mắn thay, các thông báo lỗi rất đơn giản và cách khắc phục rất đơn giản:

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

Mã nằm trong thư viện bên ngoài, chỉ có hai lần xuất hiện trong một tệp. Tôi không nghĩ rằng thay đổi vi phạm sẽ ảnh hưởng nhiều đến mã. Tuy nhiên, những người mới làm quen có thể bị nhầm lẫn .


9

Tôi sẽ rất ngạc nhiên và thất vọng về bản thân khi biết rằng bất kỳ mã C ++ nào tôi đã viết trong 12 năm qua đều gặp vấn đề như thế này. Nhưng hầu hết các trình biên dịch sẽ đưa ra cảnh báo về bất kỳ "sự thu hẹp" thời gian biên dịch nào, trừ khi tôi thiếu thứ gì đó.

Đây cũng là những chuyển đổi thu hẹp?

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

Nếu vậy, tôi nghĩ chúng có thể xuất hiện thường xuyên hơn một chút so với ví dụ từ loại động sang loại tích phân của bạn.


1
Tôi không hiểu tại sao bạn nói đây sẽ là một điều không phổ biến để tìm thấy trong mã. Logic giữa việc sử dụng -1 hoặc INT_MAX thay vì USHRT_MAX là gì? USHRT_MAX không có trong climits vào cuối năm 2010?

7

Tôi sẽ không ngạc nhiên lắm nếu ai đó bị phát hiện bởi những thứ như:

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

(trong quá trình triển khai của tôi, hai phần cuối cùng không tạo ra cùng một kết quả khi được chuyển đổi lại thành int / long, do đó đang bị thu hẹp)

Tôi không nhớ mình đã từng viết cái này bao giờ. Nó chỉ hữu ích nếu một giá trị gần đúng với các giới hạn có ích cho điều gì đó.

Điều này ít nhất cũng có vẻ hợp lý một cách mơ hồ:

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
}

nhưng nó không hoàn toàn thuyết phục, bởi vì nếu tôi biết tôi có chính xác hai giá trị, tại sao lại đặt chúng vào các mảng thay vì chỉ float floatval1 = val1, floatval1 = val2;? Tuy nhiên, động lực là gì, tại sao điều đó nên biên dịch (và hoạt động, miễn là việc mất độ chính xác nằm trong độ chính xác có thể chấp nhận được đối với chương trình), trong khi float asfloat[] = {val1, val2};không nên? Dù bằng cách nào thì tôi cũng đang khởi tạo hai float từ hai int, chỉ là trong một trường hợp, hai float là thành viên của một tập hợp.

Điều đó có vẻ đặc biệt khắc nghiệt trong trường hợp biểu thức không phải là hằng số dẫn đến chuyển đổi thu hẹp mặc dù (trên một triển khai cụ thể), tất cả các giá trị của loại nguồn đều có thể biểu diễn trong loại đích và có thể chuyển đổi trở lại giá trị ban đầu của chúng:

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

Giả sử không có lỗi, có lẽ cách khắc phục luôn là làm cho chuyển đổi rõ ràng. Trừ khi bạn đang làm điều gì đó kỳ lạ với macro, tôi nghĩ rằng trình khởi tạo mảng chỉ xuất hiện gần với kiểu của mảng hoặc ít nhất là với thứ gì đó đại diện cho kiểu, có thể phụ thuộc vào tham số mẫu. Vì vậy, một dàn diễn viên nên dễ dàng, nếu dài dòng.


8
"nếu tôi biết tôi có chính xác hai giá trị, tại sao lại đặt chúng vào các mảng" - ví dụ: vì một API như OpenGL yêu cầu nó.
Georg Fritzsche

7

Một ví dụ thực tế mà tôi đã gặp:

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

Chữ số được ngầm hiểu là doublenguyên nhân gây ra quảng cáo.


1
vì vậy hãy làm cho nó floatbằng cách viết 0.5f. ;)
underscore_d

1
@underscore_d Không hoạt động nếu floatlà tham số typedef hoặc mẫu (ít nhất là không mất độ chính xác), nhưng vấn đề là mã được viết hoạt động với ngữ nghĩa chính xác và đã trở thành lỗi với C ++ 11. Tức là, định nghĩa của một "thay đổi đột phá".
Jed

5

Hãy thử thêm -Không thu hẹp vào CFLAGS của bạn, ví dụ:

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

hoặc CPPFLAGS trong trường hợp trình biên dịch C ++ (tất nhiên nó phụ thuộc vào hệ thống xây dựng hoặc Makefile của bạn)
Mikolasan

4

Các lỗi chuyển đổi thu hẹp tương tác không tốt với các quy tắc quảng cáo số nguyên ngầm định.

Tôi đã gặp lỗi với mã trông giống như

struct char_t {
    char a;
}

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

Điều này tạo ra một lỗi chuyển đổi thu hẹp (chính xác theo tiêu chuẩn). Lý do là đó cdmặc nhiên được thăng cấp intvà kết quả intkhông được phép thu hẹp trở lại char trong danh sách trình khởi tạo.

OTOH

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

tất nhiên là vẫn ổn (nếu không thì tất cả địa ngục sẽ tan vỡ). Nhưng đáng ngạc nhiên, ngay cả

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

là ok và biên dịch mà không có cảnh báo nếu tổng của c và d nhỏ hơn CHAR_MAX. Tôi vẫn nghĩ rằng đây là một khiếm khuyết trong C ++ 11, nhưng mọi người ở đó nghĩ khác - có thể vì không dễ sửa chữa nếu không loại bỏ chuyển đổi số nguyên ngầm định (đó là dựa vào quá khứ, khi mọi người viết mã thích char a=b*c/dvà mong muốn nó hoạt động ngay cả khi (b * c)> CHAR_MAX) hoặc thu hẹp các lỗi chuyển đổi (có thể là một điều tốt).


Tôi đã gặp phải điều sau đây thực sự vô nghĩa khó chịu: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- thu hẹp chuyển đổi bên trong {}. Có thật không? Vì vậy, toán tử & cũng chuyển đổi ngầm các ký tự không dấu thành int? Tôi không quan tâm, kết quả vẫn được đảm bảo là một ký tự không dấu, argh.
Carlo Wood

" Chuyển đổi số nguyên tiềm ẩn " chương trình khuyến mãi?
curiousguy

2

Đó thực sự là một thay đổi đột phá khi trải nghiệm thực tế với tính năng này cho thấy gcc đã biến việc thu hẹp thành cảnh báo lỗi đối với nhiều trường hợp do những khó khăn trong cuộc sống thực với việc chuyển cơ sở mã C ++ 03 sang C ++ 11. Xem nhận xét này trong báo cáo lỗi gcc :

Tiêu chuẩn chỉ yêu cầu rằng "việc triển khai tuân thủ phải đưa ra ít nhất một thông báo chẩn đoán" vì vậy việc biên dịch chương trình với một cảnh báo được cho phép. Như Andrew đã nói, -Werror = thu hẹp cho phép bạn tạo ra một lỗi nếu bạn muốn.

G ++ 4.6 đã xảy ra lỗi nhưng nó đã được thay đổi thành cảnh báo cố ý cho 4.7 vì nhiều người (bao gồm cả tôi) nhận thấy rằng việc thu hẹp chuyển đổi nơi một trong những vấn đề thường gặp nhất khi cố gắng biên dịch các cơ sở mã C ++ 03 lớn thành C ++ 11 . Mã đã được hình thành trước đó như char c [] = {i, 0}; (trong đó tôi sẽ chỉ ở trong phạm vi ký tự) đã gây ra lỗi và phải đổi thành ký tự c [] = {(char) i, 0}


1

Có vẻ như GCC-4.7 không còn đưa ra lỗi thu hẹp chuyển đổi nữa mà thay vào đó là cảnh báo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.