Làm cách nào để đối phó với cảnh báo "không khớp đã ký / chưa ký" (C4018)?


80

Tôi làm việc với rất nhiều mã tính toán được viết bằng C ++ với hiệu suất cao và chi phí bộ nhớ thấp trong tâm trí. Nó sử dụng các vùng chứa STL (hầu hết vector) rất nhiều và lặp lại các vùng chứa đó hầu như trong mọi chức năng.

Mã lặp lại trông giống như sau:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}

nhưng nó tạo ra cảnh báo không khớp có dấu / chưa ký (C4018 trong Visual Studio).

Thay thế intbằng một số unsignedloại là một vấn đề vì chúng tôi thường xuyên sử dụng OpenMP pragmas và nó yêu cầu bộ đếm int.

Tôi sắp loại bỏ (hàng trăm) cảnh báo, nhưng tôi sợ rằng mình đã bỏ lỡ một số giải pháp hữu ích cho vấn đề.

Trên trình lặp . Tôi nghĩ rằng các trình lặp là tuyệt vời khi được áp dụng ở những nơi thích hợp. Mã mà tôi đang làm việc sẽ không bao giờ thay đổi các vùng chứa truy cập ngẫu nhiên thành listhoặc thứ gì đó (vì vậy việc lặp lại với vùng int ichứa đã là bất khả tri đối với vùng chứa) và sẽ luôn cần chỉ mục hiện tại. Và tất cả các mã bổ sung bạn cần nhập (chính trình lặp và chỉ mục) chỉ làm phức tạp thêm vấn đề và làm xáo trộn tính đơn giản của mã cơ bản.


1
Bạn có thể đăng một ví dụ trong đó OpenMP pragma ngăn bạn sử dụng kiểu không dấu không? Theo này nó cũng làm việc cho bất kỳ loại intergal, không chỉ int.
Billy ONeal

4
Tôi tin rằng câu hỏi này tốt hơn cho stackoverflow.
bcsanches

1
intstd::vector<T>::size_typecũng có thể khác nhau về kích thước cũng như độ ký. Ví dụ: trên hệ thống LLP64 (như Windows 64-bit), sizeof(int) == 4nhưng sizeof(std::vector<T>::size_type) == 8.
Adrian McCarthy


bản sao có thể có của stackoverflow.com/questions/8188401/…
CinCout

Câu trả lời:


60

Tất cả đều thuộc things.size()loại của bạn . Nó không phải int, nhưng size_t(nó tồn tại trong C ++, không phải trong C) tương đương với một số kiểu không dấu "thông thường", tức là unsigned intcho x86_32.

Toán tử "less" (<) không được áp dụng cho hai toán hạng có dấu khác nhau. Chỉ không có mã opcodes nào như vậy và tiêu chuẩn không chỉ định, liệu trình biên dịch có thể thực hiện chuyển đổi dấu hiệu ngầm định hay không. Vì vậy, nó chỉ coi số đã ký là chưa được ký và phát ra cảnh báo đó.

Sẽ đúng nếu viết nó như thế

for (size_t i = 0; i < things.size(); ++i) { /**/ }

hoặc thậm chí nhanh hơn

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }

17
-1 không, nó không phải size_t. Của nó là std::vector< THING >::size_type.
Raedwald

8
@Raedwald: Mặc dù bạn đúng về mặt kỹ thuật, nhưng thật khó để tưởng tượng cách triển khai tuân thủ tiêu chuẩn có thể kết thúc với các loại cơ bản khác nhau cho std::size_tstd::vector<T>::size_type.
Adrian McCarthy

4
tại sao ++ tôi được coi là tốt hơn? Trong vòng lặp for không có sự khác biệt "không"?
Shoaib

2
@ShoaibHaider, nó không quan trọng chút nào đối với các nguyên thủy mà giá trị trả về không được sử dụng. Tuy nhiên, đối với các kiểu tùy chỉnh (trong đó toán tử bị quá tải), phần tăng đăng hầu như luôn kém hiệu quả hơn (vì nó phải tạo một bản sao của đối tượng trước khi nó được tăng lên). Trình biên dịch không thể (nhất thiết) tối ưu hóa cho các loại tùy chỉnh. Vì vậy, lợi thế duy nhất là tính nhất quán (của kiểu nguyên thủy so với kiểu tùy chỉnh).
Kat

2
@zenith: Vâng, bạn nói đúng. Câu lệnh của tôi chỉ giữ cho trình cấp phát mặc định. Trình phân bổ tùy chỉnh có thể sử dụng thứ gì đó khác ngoài std :: size_t, nhưng tôi tin rằng nó vẫn phải là loại tích phân không dấu và nó có thể không thể đại diện cho một phạm vi lớn hơn std :: size_t, vì vậy vẫn an toàn khi sử dụng std :: size_t làm kiểu cho chỉ mục vòng lặp.
Adrian McCarthy

13

Tốt nhất, tôi sẽ sử dụng một cấu trúc như thế này để thay thế:

for (std::vector<your_type>::const_iterator i = things.begin(); i != things.end(); ++i)
{
  // if you ever need the distance, you may call std::distance
  // it won't cause any overhead because the compiler will likely optimize the call
  size_t distance = std::distance(things.begin(), i);
}

Điều này có một ưu điểm là mã của bạn đột nhiên trở thành vùng chứa bất khả tri.

Và liên quan đến vấn đề của bạn, nếu một số thư viện bạn sử dụng yêu cầu bạn sử dụng intở nơi unsigned intphù hợp hơn, thì API của chúng rất lộn xộn. Dù sao, nếu bạn chắc chắn rằng những điều đó intluôn tích cực, bạn có thể chỉ cần làm:

int int_distance = static_cast<int>(distance);

Điều này sẽ chỉ định rõ ràng ý định của bạn cho trình biên dịch: nó sẽ không làm bạn khó chịu với các cảnh báo nữa.


1
Tôi luôn cần khoảng cách. Có lẽ static_cast<int>(things.size())có thể là giải pháp, nếu không có những giải pháp khác.
Andrew T

@Andrew: Nếu bạn quyết định loại bỏ cảnh báo, cách tốt nhất có lẽ là sử dụng một pragma cụ thể của trình biên dịch (trên MSVC, đây sẽ là a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)) thay vì sử dụng một diễn viên không cần thiết. (Các diễn viên che giấu lỗi chính đáng, được không?;))
Billy ONeal vào

Không đúng. Cast! = Chuyển đổi. Cảnh báo là cảnh báo về một chuyển đổi ngầm được tiêu chuẩn cho phép có thể không an toàn. Tuy nhiên, một diễn viên mời các chuyển đổi rõ ràng đến bên có thể không an toàn hơn những gì được cảnh báo đề cập ban đầu. Nói một cách chính xác, ít nhất là trên x86, sẽ không có chuyển đổi nào xảy ra - bộ xử lý không quan tâm đến việc bạn đang coi một phần cụ thể của bộ nhớ là có dấu hay không dấu, miễn là bạn sử dụng đúng hướng dẫn để làm việc với nó.
Billy ONeal

Khi vùng chứa bất khả tri gây ra độ phức tạp O (N ^ 2) (và trong ví dụ của bạn thì có, vì khoảng cách () là O (N) cho danh sách <>), tôi không chắc đó là một lợi thế :-(.
No-Bugs Hare

@ No-BugsHare Đó chính xác là điểm: chúng tôi không thể chắc chắn. Nếu OP có ít phần tử, thì nó có thể là tuyệt vời. Nếu anh ta có hàng triệu cái đó, có lẽ không nhiều lắm. Cuối cùng thì chỉ có việc lập hồ sơ mới có thể biết được nhưng tin tốt là: bạn luôn có thể tối ưu hóa mã có thể bảo trì!
vào

9

Nếu bạn không thể / sẽ không sử dụng vòng lặp và nếu bạn không thể / sẽ không sử dụng std::size_tcho các chỉ số vòng lặp, thực hiện một .size()đến intchức năng chuyển đổi văn bản giả định và không chuyển đổi một cách rõ ràng để bịt miệng những cảnh báo trình biên dịch.

#include <cassert>
#include <cstddef>
#include <limits>

// When using int loop indexes, use size_as_int(container) instead of
// container.size() in order to document the inherent assumption that the size
// of the container can be represented by an int.
template <typename ContainerType>
/* constexpr */ int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

Sau đó, bạn viết các vòng lặp của mình như thế này:

for (int i = 0; i < size_as_int(things); ++i) { ... }

Việc khởi tạo mẫu hàm này gần như chắc chắn sẽ được nội tuyến. Trong các bản dựng gỡ lỗi, giả định sẽ được kiểm tra. Trong các bản dựng phát hành, nó sẽ không như vậy và mã sẽ nhanh như thể bạn gọi trực tiếp size (). Không phiên bản nào sẽ đưa ra cảnh báo trình biên dịch và nó chỉ là một sửa đổi nhỏ đối với vòng lặp thành ngữ.

Nếu bạn cũng muốn nắm bắt các lỗi giả định trong phiên bản phát hành, bạn có thể thay thế xác nhận bằng một câu lệnh if có nội dung giống như vậy std::out_of_range("container size exceeds range of int").

Lưu ý rằng điều này giải quyết được cả so sánh có dấu / không dấu cũng như vấn đề tiềm ẩn sizeof(int)! = sizeof(Container::size_type). Bạn có thể bật tất cả các cảnh báo của mình và sử dụng chúng để phát hiện các lỗi thực sự trong các phần khác của mã.


6

Bạn có thể dùng:

  1. size_t type, để xóa thông báo cảnh báo
  2. vòng lặp + khoảng cách (giống như gợi ý đầu tiên)
  3. chỉ trình lặp
  4. đối tượng chức năng

Ví dụ:

// simple class who output his value
class ConsoleOutput
{
public:
  ConsoleOutput(int value):m_value(value) { }
  int Value() const { return m_value; }
private:
  int m_value;
};

// functional object
class Predicat
{
public:
  void operator()(ConsoleOutput const& item)
  {
    std::cout << item.Value() << std::endl;
  }
};

void main()
{
  // fill list
  std::vector<ConsoleOutput> list;
  list.push_back(ConsoleOutput(1));
  list.push_back(ConsoleOutput(8));

  // 1) using size_t
  for (size_t i = 0; i < list.size(); ++i)
  {
    std::cout << list.at(i).Value() << std::endl;
  }

  // 2) iterators + distance, for std::distance only non const iterators
  std::vector<ConsoleOutput>::iterator itDistance = list.begin(), endDistance = list.end();
  for ( ; itDistance != endDistance; ++itDistance)
  {
    // int or size_t
    int const position = static_cast<int>(std::distance(list.begin(), itDistance));
    std::cout << list.at(position).Value() << std::endl;
  }

  // 3) iterators
  std::vector<ConsoleOutput>::const_iterator it = list.begin(), end = list.end();
  for ( ; it != end; ++it)
  {
    std::cout << (*it).Value() << std::endl;
  }
  // 4) functional objects
  std::for_each(list.begin(), list.end(), Predicat());
}

3

Tôi cũng có thể đề xuất giải pháp sau cho C ++ 11.

for (auto p = 0U; p < sys.size(); p++) {

}

(C ++ không đủ thông minh để tự động p = 0, vì vậy tôi phải đặt p = 0U ....)


1
+1 cho C ++ 11. Trừ khi có một tốt lý do bạn không thể sử dụng C ++ 11, tôi nghĩ rằng nó là tốt nhất để sử dụng các tính năng mới ... họ có nghĩa là để được một trợ giúp lớn. Và chắc chắn sử dụng for (auto thing : vector_of_things)nếu bạn không thực sự cần chỉ mục.
parker.sikand

Nhưng điều đó chỉ giải quyết được vấn đề ký kết. Sẽ không hữu ích nếu size()trả về một kiểu lớn hơn int unsigned, điều này cực kỳ phổ biến.
Adrian McCarthy

3

Tôi sẽ cho bạn một ý tưởng tốt hơn

for(decltype(things.size()) i = 0; i < things.size(); i++){
                   //...
}

decltype

Kiểm tra kiểu đã khai báo của một thực thể hoặc kiểu và loại giá trị của một biểu thức.

Vì vậy, nó suy ra loại things.size()isẽ là một loại giống như things.size(). Vì vậy, i < things.size()sẽ được thực thi mà không có bất kỳ cảnh báo nào


0

Tôi đã có một vấn đề tương tự. Sử dụng size_t không hoạt động. Tôi đã thử cái khác phù hợp với tôi. (như sau)

for(int i = things.size()-1;i>=0;i--)
{
 //...
}

0

Tôi sẽ chỉ làm

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
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.