size_t hoặc int cho kích thước, chỉ mục, v.v.


15

Trong C ++, size_t(hoặc, chính xác hơn T::size_typelà "thường" size_t; nghĩa là một unsignedloại) được sử dụng làm giá trị trả về cho size(), đối số operator[], v.v. (xem std::vector, et. Al.)

Mặt khác, các ngôn ngữ .NET sử dụng int(và, tùy chọn long) cho cùng một mục đích; trên thực tế, ngôn ngữ CLS-compliant được không cần thiết để hỗ trợ các kiểu unsigned .

Cho rằng .NET mới hơn C ++, một cái gì đó cho tôi biết rằng có thể có vấn đề khi sử dụng unsigned intngay cả đối với những thứ "không thể" có thể âm như chỉ số hoặc độ dài của mảng. Cách tiếp cận C ++ có phải là "tạo tác lịch sử" để tương thích ngược không? Hoặc có sự đánh đổi thiết kế thực sự và có ý nghĩa giữa hai phương pháp?

Vì sao vấn đề này? Chà ... tôi nên sử dụng cái gì cho một lớp đa chiều mới trong C ++; size_thay int?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
Đáng chú ý: ở một số nơi trong .NET Framework, -1được trả về từ các hàm trả về một chỉ mục, để chỉ ra "không tìm thấy" hoặc "ngoài phạm vi". Nó cũng được trả về từ các Compare()chức năng (thực hiện IComparable). Một int 32 bit được coi là đi để gõ cho một số chung, vì những gì tôi hy vọng là lý do rõ ràng.
Robert Harvey

Câu trả lời:


9

Cho rằng .NET mới hơn C ++, một cái gì đó cho tôi biết rằng có thể có vấn đề khi sử dụng int unsign ngay cả đối với những thứ "không thể" có thể âm như chỉ số hoặc độ dài của mảng.

Đúng. Đối với một số loại ứng dụng nhất định như xử lý ảnh hoặc xử lý mảng, thường cần phải truy cập các phần tử liên quan đến vị trí hiện tại:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

Trong các loại ứng dụng này, bạn không thể thực hiện kiểm tra phạm vi với các số nguyên không dấu mà không suy nghĩ cẩn thận:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Thay vào đó bạn phải sắp xếp lại biểu thức kiểm tra phạm vi của bạn. Đó là sự khác biệt chính. Các lập trình viên cũng phải nhớ các quy tắc chuyển đổi số nguyên. Khi nghi ngờ, hãy đọc lại http://en.cppreference.com/w/cpp/lingu/operator_arithatures#Conversions

Rất nhiều ứng dụng không cần sử dụng các chỉ số mảng rất lớn, nhưng chúng cần thực hiện kiểm tra phạm vi. Hơn nữa, rất nhiều lập trình viên không được đào tạo để thực hiện biểu hiện sắp xếp lại thể dục dụng cụ này. Một cơ hội bị bỏ lỡ duy nhất mở ra cánh cửa để khai thác.

C # thực sự được thiết kế cho những ứng dụng không cần nhiều hơn 2 ^ 31 phần tử trên mỗi mảng. Ví dụ, một ứng dụng bảng tính không cần phải xử lý nhiều hàng, cột hoặc ô đó. C # xử lý giới hạn trên bằng cách có số học được kiểm tra tùy chọn có thể được bật cho một khối mã với từ khóa mà không gây rối với các tùy chọn trình biên dịch. Vì lý do này, C # ủng hộ việc sử dụng số nguyên đã ký. Khi những quyết định này được xem xét hoàn toàn, nó có ý nghĩa tốt.

C ++ đơn giản là khác nhau và khó lấy mã chính xác hơn.

Về tầm quan trọng thực tế của việc cho phép số học đã ký để loại bỏ vi phạm tiềm ẩn "nguyên tắc ít gây ngạc nhiên nhất", một trường hợp điển hình là OpenCV, sử dụng số nguyên 32 bit đã ký cho chỉ số phần tử ma trận, kích thước mảng, số kênh pixel, v.v. xử lý là một ví dụ về miền lập trình sử dụng chỉ số mảng tương đối nhiều. Dòng dưới số nguyên không được ký (kết quả âm được bao quanh) sẽ làm phức tạp nghiêm trọng việc thực hiện thuật toán.


Đây chính xác là tình huống của tôi; cảm ơn các ví dụ cụ thể (Vâng, tôi biết điều này, nhưng nó có thể hữu ích để có "cơ quan chức năng cao hơn" để trích dẫn.)
Ðаn

1
@Dan: nếu bạn cần trích dẫn một cái gì đó, bài đăng này sẽ tốt hơn.
rwong

1
@Dan: John Regehr đang tích cực nghiên cứu vấn đề này bằng ngôn ngữ lập trình. Xem blog.regehr.org/archives/1401
rwong

Có nhiều ý kiến ​​trái ngược: gustyt.wordpress.com/2013/07/15/ từ
rwong

14

Câu trả lời này thực sự phụ thuộc vào người sẽ sử dụng mã của bạn và tiêu chuẩn họ muốn xem.

size_t là một kích thước nguyên với mục đích:

Kiểu size_tnày là một kiểu số nguyên không dấu được xác định do triển khai đủ lớn để chứa kích thước tính theo byte của bất kỳ đối tượng nào. (Đặc tả C ++ 11 18.2.6)

Vì vậy, bất cứ lúc nào bạn muốn làm việc với kích thước của các đối tượng tính bằng byte, bạn nên sử dụng size_t. Bây giờ, trong nhiều trường hợp, bạn không sử dụng các thứ nguyên / chỉ mục này để đếm byte, nhưng hầu hết các nhà phát triển chọn sử dụng size_tở đó để thống nhất.

Lưu ý rằng bạn phải luôn luôn sử dụng size_tnếu lớp của bạn dự định có giao diện của lớp STL. Tất cả các lớp STL trong đặc tả sử dụng size_t. Nó có giá trị cho các trình biên dịch để typedef size_tđược unsigned int, và nó cũng có giá trị cho nó được typedefed tới unsigned long. Nếu bạn sử dụng inthoặc longtrực tiếp, cuối cùng bạn sẽ chạy vào trình biên dịch nơi một người nghĩ rằng lớp của bạn theo phong cách của STL bị mắc kẹt vì bạn không tuân theo tiêu chuẩn.

Đối với việc sử dụng các loại đã ký, có một vài lợi thế:

  • Tên ngắn hơn - mọi người thực sự dễ dàng nhập int, nhưng khó khăn hơn nhiều để làm lộn xộn mã unsigned int.
  • Một số nguyên cho mỗi kích thước - Chỉ có một số nguyên 32 bit tuân thủ CLS, đó là Int32. Trong C ++, có hai ( int32_tuint32_t). Điều này có thể làm cho khả năng tương tác API đơn giản hơn

Nhược điểm lớn của các loại đã ký là một điều hiển nhiên: bạn mất một nửa tên miền. Một số đã ký không thể được tính cao bằng số không dấu. Khi C / C ++ xuất hiện, điều này rất quan trọng. Một thứ cần thiết để có thể giải quyết toàn bộ khả năng của bộ xử lý và để làm được điều đó bạn cần sử dụng các số không dấu.

Đối với các loại ứng dụng .NET được nhắm mục tiêu, không có nhu cầu về chỉ mục không dấu tên miền đầy đủ. Nhiều mục đích cho những con số như vậy đơn giản là không hợp lệ trong một ngôn ngữ được quản lý (tập hợp bộ nhớ đến trong tâm trí). Ngoài ra, khi .NET ra đời, máy tính 64 bit rõ ràng là tương lai. Chúng ta còn lâu mới cần đến toàn bộ số nguyên 64 bit, nên hy sinh một bit không còn đau đớn như trước. Nếu bạn thực sự cần 4 tỷ chỉ mục, bạn chỉ cần chuyển sang sử dụng số nguyên 64 bit. Tệ nhất, bạn chạy nó trên máy 32 bit và hơi chậm.

Tôi xem thương mại là một trong những tiện lợi. Nếu bạn tình cờ có đủ sức mạnh tính toán mà bạn không ngại lãng phí một chút loại chỉ mục mà bạn sẽ không bao giờ sử dụng, thì thật tiện lợi khi chỉ cần gõ inthoặc longbỏ đi. Nếu bạn thấy bạn thực sự muốn chút cuối cùng đó, thì có lẽ bạn nên chú ý đến chữ ký của các số của bạn.


giả sử việc thực hiện size()return bar_ * baz_;; không phải bây giờ tạo ra một vấn đề tiềm ẩn với tràn số nguyên (bao quanh) mà tôi sẽ không có nếu tôi không sử dụng size_t?
Đаn

5
@Dan Bạn có thể xây dựng các trường hợp như thế trong đó có ints không dấu sẽ quan trọng và trong những trường hợp đó, tốt nhất là sử dụng các tính năng ngôn ngữ đầy đủ để giải quyết nó. Tuy nhiên, tôi phải nói rằng nó sẽ là một công trình thú vị để có một lớp trong đó bar_ * baz_có thể tràn một số nguyên đã ký nhưng không phải là một số nguyên không dấu. Giới hạn bản thân với C ++, đáng chú ý là tràn không dấu được xác định trong thông số kỹ thuật, nhưng tràn đã ký là hành vi không xác định, vì vậy, nếu số học modulo của các số nguyên không dấu là mong muốn, chắc chắn sử dụng chúng, bởi vì nó thực sự được xác định!
Cort Ammon - Phục hồi Monica

1
@ Dan - nếu các size()tràn những nhân, bạn đang ở đất ngôn ngữ UB. (và trong fwrapvchế độ, xem tiếp theo :) Khi đó , chỉ với một chút wee nhỏ hơn, nó tràn các unsigned nhân, các ngươi trong đất sử dụng mã lỗi - bạn sẽ quay trở lại một kích thước không có thật. Vì vậy, tôi không nghĩ rằng không dấu mua nhiều ở đây.
Martin Ba

4

Tôi nghĩ rằng câu trả lời của rwong ở trên đã làm nổi bật các vấn đề.

Tôi sẽ thêm 002 của mình:

  • size_t, đó là, một kích thước mà ...

    có thể lưu trữ kích thước tối đa của một đối tượng lý thuyết có thể thuộc bất kỳ loại nào (bao gồm cả mảng).

    ... chỉ được yêu cầu cho các chỉ số phạm vi khi sizeof(type)==1, nghĩa là, nếu bạn đang xử lý các loại byte ( char). (Nhưng, chúng tôi lưu ý, nó có thể nhỏ hơn loại ptr :

  • Như vậy, xxx::size_typecó thể được sử dụng trong 99,9% trường hợp ngay cả khi đó là loại có kích thước đã ký. (so sánh ssize_t)
  • Thực tế là std::vectorvà bạn bè đã chọn size_t, một loại không dấu , cho kích thước và lập chỉ mục được một số người coi là một lỗ hổng thiết kế. Tôi đồng tình. (Nghiêm túc, hãy dành 5 phút và xem cuộc nói chuyện chớp nhoáng CppCon 2016: Jon Kalb ảo không dấu: Hướng dẫn về mã tốt hơn " .)
  • Khi bạn thiết kế API C ++ ngay hôm nay, bạn đang ở một nơi chật hẹp: Sử dụng size_tđể phù hợp với Thư viện tiêu chuẩn hoặc sử dụng ( đã ký ) intptr_thoặc ssize_tđể tính toán lập chỉ mục dễ bị lỗi và ít bị lỗi.
  • Không sử dụng int32 hoặc int64 - sử dụng intptr_tnếu bạn muốn đăng nhập và muốn kích thước từ máy hoặc sử dụng ssize_t.

Để trả lời trực tiếp câu hỏi, nó không hoàn toàn là một "vật phẩm lịch sử", vì vấn đề lý thuyết cần giải quyết hơn một nửa không gian địa chỉ ("lập chỉ mục", hoặc) phải được giải quyết bằng cách nào đó bằng ngôn ngữ cấp thấp như C ++.

Nhìn chung, tôi, cá nhân , nghĩ rằng, đó một lỗ hổng thiết kế mà Thư viện tiêu chuẩn sử dụng không dấu ở size_tkhắp mọi nơi ngay cả khi nó không biểu thị kích thước bộ nhớ thô, nhưng dung lượng dữ liệu được nhập, như cho các bộ sưu tập:

  • đưa ra quy tắc quảng cáo số nguyên C ++ ->
  • các loại không dấu chỉ không tạo ra các ứng cử viên tốt cho các loại "ngữ nghĩa" cho một cái gì đó như kích thước không được ký hiệu về mặt ngữ nghĩa.

Tôi sẽ nhắc lại lời khuyên của Jon ở đây:

  • Chọn loại cho các hoạt động họ hỗ trợ (không phải phạm vi của các giá trị). (* 1)
  • Không sử dụng các loại không dấu trong API của bạn. Điều này che giấu lỗi không có lợi ích ngược.
  • Không sử dụng "không dấu" cho số lượng. (* 2)

(* 1) tức là không dấu == bitmask, không bao giờ thực hiện phép toán trên nó (ở đây đánh vào ngoại lệ đầu tiên - bạn có thể cần một bộ đếm kết thúc - đây phải là một loại không dấu.)

(* 2) số lượng có nghĩa là một cái gì đó bạn đếm và / hoặc làm toán.


Bạn có ý nghĩa gì với "bộ nhớ phẳng đầy đủ"? Ngoài ra, chắc chắn bạn không muốn ssize_t, được định nghĩa là mặt dây chuyền đã ký size_tthay vì intptr_t, có thể lưu trữ bất kỳ con trỏ (không phải thành viên-) nào và do đó có thể lớn hơn?
Ded repeatator

@Ded repeatator - Chà tôi đoán rằng tôi có thể đã nhận được size_tđịnh nghĩa hơi sai lầm. Xem size_t so với intptren.cppreference.com/w/cpp/types/size_t Đã học được điều gì đó mới hôm nay. :-) Tôi nghĩ phần còn lại của các đối số, tôi sẽ xem liệu tôi có thể sửa các loại được sử dụng không.
Martin Ba

0

Tôi sẽ chỉ thêm rằng vì lý do hiệu suất mà tôi thường sử dụng size_t, để đảm bảo rằng tính toán sai gây ra dòng chảy, có nghĩa là cả hai kiểm tra phạm vi (dưới 0 và trên kích thước ()) có thể được giảm xuống một:

sử dụng chữ ký int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

sử dụng int unsign:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
Bạn thực sự muốn giải thích một cách kỹ lưỡng hơn.
Martin Ba

Để làm cho câu trả lời hữu ích hơn, có lẽ bạn có thể mô tả cách giới hạn mảng số nguyên hoặc so sánh bù (đã ký và không dấu) trông giống như trong mã máy từ các nhà cung cấp trình biên dịch khác nhau. Có nhiều trình biên dịch và trang web C ++ trực tuyến có thể hiển thị mã máy được biên dịch tương ứng cho mã C ++ và cờ trình biên dịch đã cho.
rwong

Tôi đã cố gắng để giải thích điều này nhiều hơn nữa.
asger
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.