Khi nào nên sử dụng std :: size_t?


201

Tôi chỉ tự hỏi tôi nên sử dụng std::size_tcho các vòng lặp và công cụ thay vì int? Ví dụ:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

Nói chung, thực hành tốt nhất về khi nào nên sử dụng std::size_t?

Câu trả lời:


186

Một nguyên tắc nhỏ là dành cho bất cứ điều gì bạn cần so sánh trong điều kiện vòng lặp với điều gì đó tự nhiên là std::size_tchính nó.

std::size_tlà loại sizeofbiểu thức bất kỳ và được đảm bảo có thể biểu thị kích thước tối đa của bất kỳ đối tượng nào (bao gồm bất kỳ mảng nào) trong C ++. Bằng cách mở rộng, nó cũng được đảm bảo đủ lớn cho bất kỳ chỉ mục mảng nào, vì vậy đây là loại tự nhiên cho một vòng lặp theo chỉ mục trên một mảng.

Nếu bạn chỉ đếm đến một số thì có thể tự nhiên hơn khi sử dụng loại biến chứa số đó hoặc inthoặc unsigned int(nếu đủ lớn) vì đây phải là kích thước tự nhiên cho máy.


41
Điều đáng nói là không sử dụng size_tkhi nào bạn có thể dẫn đến lỗi bảo mật .
BlueRaja - Daniel Pflughoeft

5
Không chỉ là "tự nhiên", mà việc trộn loại đã ký và không dấu cũng có thể dẫn đến các lỗi bảo mật. Các chỉ số không được chỉ định là một nỗi đau để xử lý và một lý do chính đáng để sử dụng một lớp vectơ tùy chỉnh.
Jo So

2
@JoSo Cũng có ssize_tcác giá trị đã ký.
EntangledLoops

70

size_tlà loại kết quả của sizeoftoán tử.

Sử dụng size_tcho các biến mô hình kích thước hoặc chỉ mục trong một mảng. size_ttruyền đạt ngữ nghĩa: bạn biết ngay nó đại diện cho một kích thước tính bằng byte hoặc một chỉ mục, thay vì chỉ một số nguyên khác.

Ngoài ra, sử dụng size_tđể thể hiện kích thước theo byte giúp làm cho mã di động.


32

Các size_tloại có nghĩa là để xác định kích thước của một cái gì đó để nó tự nhiên để sử dụng nó, ví dụ, nhận được độ dài của một chuỗi và sau đó chế biến mỗi nhân vật:

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

Bạn làm phải xem ra cho điều kiện biên tất nhiên, vì nó là một loại unsigned. Ranh giới vào cuối đầu không phải là thường là quan trọng vì mức tối đa là thường lớn (mặc dù nó có thể đạt được điều đó). Hầu hết mọi người chỉ sử dụng một intloại cho điều đó bởi vì họ hiếm khi có cấu trúc hoặc mảng đủ lớn để vượt quá khả năng của điều đó int.

Nhưng coi chừng những thứ như:

for (size_t i = strlen (str) - 1; i >= 0; i--)

điều này sẽ gây ra một vòng lặp vô hạn do hành vi gói các giá trị không dấu (mặc dù tôi đã thấy các trình biên dịch cảnh báo chống lại điều này). Điều này cũng có thể được giảm bớt bởi (hơi khó hiểu nhưng ít nhất là miễn dịch với các vấn đề gói):

for (size_t i = strlen (str); i-- > 0; )

Bằng cách chuyển phần giảm xuống thành hiệu ứng phụ sau kiểm tra của điều kiện tiếp tục, điều này thực hiện kiểm tra tiếp tục trên giá trị trước khi giảm, nhưng vẫn sử dụng giá trị giảm bên trong vòng lặp (đó là lý do tại sao vòng lặp chạy từ len .. 1thay vì len-1 .. 0).


14
Nhân tiện, đó là một cách thực hành tồi để gọi strlenmỗi lần lặp của một vòng lặp. :) Bạn có thể làm một cái gì đó như thế này:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil

1
Ngay cả khi đó là một loại đã ký, bạn phải coi chừng các điều kiện biên, thậm chí có thể còn nhiều hơn vì tràn số nguyên đã ký là hành vi không xác định.
Adrian McCarthy

2
Đếm ngược chính xác có thể được thực hiện theo cách (khét tiếng) sau:for (size_t i = strlen (str); i --> 0;)
Jo So

1
@JoSo, đó thực sự là một mẹo khá gọn gàng mặc dù tôi không chắc là tôi thích phần giới thiệu của -->toán tử " go to" (xem stackoverflow.com/questions/1642028/ Lỗi ). Đã kết hợp đề nghị của bạn vào câu trả lời.
paxdiablo

Bạn có thể làm một việc đơn giản if (i == 0) break;ở cuối vòng lặp for không (ví dụ: for (size_t i = strlen(str) - 1; ; --i)(Tôi thích bạn hơn, nhưng chỉ tự hỏi liệu điều này có hoạt động tốt không)
RastaJedi

13

Theo định nghĩa, size_tlà kết quả của sizeoftoán tử. size_tđược tạo ra để chỉ kích thước.

Số lần bạn làm một cái gì đó (10, trong ví dụ của bạn) không phải là về kích thước, vậy tại sao lại sử dụng size_t? int, hoặc unsigned int, nên ok.

Tất nhiên nó cũng có liên quan những gì bạn làm với ibên trong vòng lặp. Nếu bạn chuyển nó đến một chức năng unsigned int, ví dụ, chọn unsigned int.

Trong mọi trường hợp, tôi khuyên bạn nên tránh chuyển đổi loại ngầm định. Thực hiện tất cả các chuyển đổi loại rõ ràng.


10

size_tlà một cách rất dễ đọc để chỉ định kích thước kích thước của một mục - độ dài của chuỗi, số byte mà con trỏ sử dụng, v.v. Nó cũng có thể di chuyển trên các nền tảng - bạn sẽ thấy cả 64 bit và 32 bit đều hoạt động tốt với các chức năng của hệ thống và size_t- một cái gì đó unsigned intcó thể không làm (ví dụ khi nào bạn nên sử dụngunsigned long


9

câu trả lời ngắn:

hầu như không bao giờ

câu trả lời dài:

Bất cứ khi nào bạn cần phải có một vectơ char lớn hơn 2gb trên hệ thống 32 bit. Trong mọi trường hợp sử dụng khác, sử dụng loại đã ký sẽ an toàn hơn nhiều so với sử dụng loại không dấu.

thí dụ:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

Chữ ký tương đương size_tptrdiff_t, không int. Nhưng sử dụng intvẫn tốt hơn nhiều trong hầu hết các trường hợp so với size_t. ptrdiff_tlong trên hệ thống 32 và 64 bit.

Điều này có nghĩa là bạn luôn phải chuyển đổi sang và từ size_t bất cứ khi nào bạn tương tác với một std :: container, không đẹp lắm. Nhưng trong một hội nghị bản địa đang diễn ra, các tác giả của c ++ đã đề cập rằng thiết kế std :: vector với size_t không dấu là một sai lầm.

Nếu trình biên dịch của bạn cung cấp cho bạn các cảnh báo về chuyển đổi ngầm định từ ptrdiff_t sang size_t, bạn có thể làm cho nó rõ ràng bằng cú pháp của hàm tạo:

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

nếu chỉ muốn lặp lại một bộ sưu tập, không có giới hạn, hãy sử dụng phạm vi dựa trên:

for(const auto& d : data) {
    [...]
}

ở đây một số từ của Bjarne Stroustrup (tác giả C ++) tại bản địa

Đối với một số người, lỗi thiết kế đã ký / không dấu này trong STL là đủ lý do, để không sử dụng std :: vector, mà thay vào đó là một triển khai riêng.


1
Tôi hiểu họ đến từ đâu, nhưng tôi vẫn nghĩ nó thật kỳ lạ khi viết for(int i = 0; i < get_size_of_stuff(); i++). Bây giờ, chắc chắn, bạn có thể không muốn thực hiện nhiều vòng lặp thô, nhưng - thôi nào, bạn cũng sử dụng chúng.
einpoklum

Lý do duy nhất tôi sử dụng các vòng lặp thô là vì thư viện thuật toán c ++ được thiết kế khá tệ. Có những ngôn ngữ, như Scala, có một thư viện phát triển hơn và tốt hơn để vận hành trên các bộ sưu tập. Sau đó, trường hợp sử dụng các vòng lặp thô được loại bỏ khá nhiều. Cũng có những cách tiếp cận để cải thiện c ++ với STL mới và tốt hơn, nhưng tôi nghi ngờ điều này sẽ xảy ra trong thập kỷ tới.
Arne

1
Tôi nhận được rằng i = 0; khẳng định (i-1, MAX_INT); nhưng tôi không hiểu tại sao bạn nói "nếu tôi đã có một dòng chảy, điều này trở thành sự thật" bởi vì hành vi của số học trên các số nguyên không dấu luôn được xác định, nghĩa là. kết quả là modulo kết quả kích thước của số nguyên có thể biểu diễn lớn nhất. Vì vậy, nếu i == 0, thì i-- trở thành MAX_INT và sau đó i ++ trở thành 0 lần nữa.
mabraham

@mabraham Tôi đã xem xét cẩn thận, và bạn nói đúng, mã của tôi không phải là tốt nhất để hiển thị vấn đề. Thông thường, điều này x + 1 < ytương đương với x < y - 1, nhưng chúng không có số nguyên không mong muốn. Điều đó có thể dễ dàng giới thiệu các lỗi khi mọi thứ được chuyển đổi được coi là tương đương.
Arne

8

Sử dụng std :: size_t để lập chỉ mục / đếm mảng kiểu C.

Đối với các thùng chứa STL, bạn sẽ có (ví dụ) vector<int>::size_type, nên được sử dụng để lập chỉ mục và đếm các yếu tố vectơ.

Trong thực tế, chúng thường là cả hai số nguyên không dấu, nhưng nó không được bảo đảm, đặc biệt là khi sử dụng các cấp phát tùy chỉnh.


2
Với gcc trên linux, std::size_tthường là unsigned long(8 byte trên hệ thống 64 bit) chứ không phải unisgned int(4 byte).
rafak

5
Mảng kiểu C không được lập chỉ mục bởi size_tvì các chỉ mục có thể âm. Tuy nhiên, người ta có thể sử dụng size_tví dụ của một mảng như vậy nếu một người không muốn phủ định.
Johannes Schaub - litb

So sánh trên u64 có nhanh như so sánh trên u32 không? Tôi đã tính thời gian xử phạt hiệu suất nghiêm trọng vì sử dụng u8 và u16 làm trọng tâm vòng lặp, nhưng tôi không biết liệu Intel có thực hiện hành vi của họ với nhau trên 64 không.
Crashworks

2
Vì lập chỉ mục mảng kiểu C tương đương với việc sử dụng toán tử +trên các con trỏ, nên có vẻ như đó ptrdiff_tlà cách sử dụng cho các chỉ mục.
Pavel Minaev

8
Đối với vector<T>::size_type(và ditto cho tất cả các container khác), nó thực sự khá vô dụng, bởi vì nó được bảo đảm một cách hiệu quả size_t- đó là do typedef'd Allocator::size_type, và đối với các hạn chế đối với các container xem 20.1.5 / 4 - đặc biệt, size_typephải được size_t, và difference_typephải được ptrdiff_t. Tất nhiên, mặc định std::allocator<T>thỏa mãn những yêu cầu đó. Vì vậy, chỉ cần sử dụng ngắn hơn size_tvà đừng bận tâm với phần còn lại của lô :)
Pavel Minaev

7

Hầu hết các máy tính sẽ sớm có kiến ​​trúc 64 bit với HĐH 64 bit: es chạy các chương trình hoạt động trên các thùng chứa hàng tỷ phần tử. Sau đó, bạn phải sử dụng size_tthay vì intchỉ mục vòng lặp, nếu không, chỉ mục của bạn sẽ bao quanh ở phần tử 2 ^ 32: trên cả hai hệ thống 32 và 64 bit.

Chuẩn bị cho tương lai!


Đối số của bạn chỉ đi xa như ý nghĩa người ta cần long intchứ không phải là một int. Nếu size_tcó liên quan trên HĐH 64 bit thì nó cũng có liên quan trên HĐH 32 bit.
einpoklum

4

Khi sử dụng size_t hãy cẩn thận với biểu thức sau

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

Bạn sẽ nhận được sai trong biểu thức if bất kể giá trị nào bạn có cho x. Phải mất vài ngày tôi mới nhận ra điều này (mã đơn giản đến mức tôi không làm bài kiểm tra đơn vị), mặc dù chỉ mất vài phút để tìm ra nguồn gốc của vấn đề. Không chắc chắn là tốt hơn để làm một diễn viên hoặc sử dụng số không.

if ((int)(i-x) > -1 or (i-x) >= 0)

Cả hai cách nên làm việc. Đây là lần chạy thử của tôi

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

Đầu ra: i-7 = 18446744073709551614 (int) (i-7) = - 2

Tôi muốn ý kiến ​​của người khác.


2
xin lưu ý rằng đó (int)(i - 7)là một dòng chảy được chuyển sang intsau đó, trong khi đó int(i) - 7không phải là một dòng chảy kể từ lần đầu tiên bạn chuyển đổi ithành một int, và sau đó trừ đi 7. Ngoài ra tôi thấy ví dụ của bạn khó hiểu.
hochl

Quan điểm của tôi là int thường an toàn hơn khi bạn thực hiện phép trừ.
Kemin Zhou

4

size_t được trả về bởi các thư viện khác nhau để chỉ ra rằng kích thước của container đó khác không. Bạn sử dụng nó khi bạn nhận được một lần trở lại: 0

Tuy nhiên, trong ví dụ của bạn ở trên lặp trên size_t là một lỗi tiềm ẩn. Hãy xem xét những điều sau đây:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

việc sử dụng các số nguyên không dấu có khả năng tạo ra các loại vấn đề tinh tế này. Do đó imho tôi chỉ thích sử dụng size_t khi tôi tương tác với các thùng chứa / loại yêu cầu.


Everone dường như sử dụng size_t trong vòng lặp mà không bận tâm về lỗi này và tôi đã học được điều này một cách khó khăn
Pranjal Gupta

-2

size_tlà một loại không dấu có thể giữ giá trị số nguyên tối đa cho kiến ​​trúc của bạn, vì vậy nó được bảo vệ khỏi tràn số nguyên do ký hiệu (ký hiệu int 0x7FFFFFFFtăng lên 1 sẽ cho bạn -1) hoặc kích thước ngắn (không dấu int 0xFFFF tăng thêm 1 sẽ cung cấp cho bạn 0).

Nó chủ yếu được sử dụng trong số học mảng / vòng lặp / số học địa chỉ và như vậy. Các chức năng như memset()size_tchỉ chấp nhận , vì về lý thuyết bạn có thể có một khối bộ nhớ có kích thước 2^32-1(trên nền tảng 32 bit).

Đối với các vòng đơn giản như vậy, đừng bận tâm và chỉ sử dụng int.


-3

size_t là một loại tích phân không dấu, có thể biểu thị số nguyên lớn nhất trên hệ thống của bạn. Chỉ sử dụng nó nếu bạn cần mảng rất lớn, ma trận, v.v.

Một số hàm trả về size_t và trình biên dịch của bạn sẽ cảnh báo bạn nếu bạn cố gắng so sánh.

Tránh điều đó bằng cách sử dụng một kiểu dữ liệu được ký / không dấu thích hợp hoặc đơn giản là typecast để hack nhanh.


4
Chỉ sử dụng nó nếu bạn muốn tránh lỗi và lỗ hổng bảo mật.
Craig McQueen

2
Nó thực sự có thể không thể đại diện cho số nguyên lớn nhất trên hệ thống của bạn.
Adrian McCarthy

-4

size_t không dấu int. vì vậy bất cứ khi nào bạn muốn int unsign bạn có thể sử dụng nó.

Tôi sử dụng nó khi tôi muốn chỉ định kích thước của mảng, bộ đếm ...

void * operator new (size_t size); is a good use of it.

10
Trên thực tế, nó không nhất thiết giống như int unsign. Nó không được ký, nhưng nó có thể lớn hơn (hoặc tôi đoán nhỏ hơn mặc dù tôi không biết bất kỳ nền tảng nào có đúng) so với int.
Todd Gamblin

Ví dụ, trên máy 64 bit size_tcó thể là số nguyên 64 bit không dấu, trong khi trên máy 32 bit, nó chỉ là số nguyên không dấu 32 bit.
HerpDerpington
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.