Size_t trong C là gì?


626

Tôi đang bị lẫn lộn size_ttrong C. Tôi biết rằng nó được trả về bởi sizeoftoán tử. Nhưng chính xác là nó? Đây có phải là một kiểu dữ liệu?

Hãy nói rằng tôi có một forvòng lặp:

for(i = 0; i < some_size; i++)

Tôi nên sử dụng int i;hay size_t i;?


11
Nếu đó là những lựa chọn duy nhất của bạn, hãy sử dụng intnếu some_sizeđược ký, size_tnếu nó không được ký.
Nate

8
@Nate Điều đó không chính xác. POSIX có loại ssize_t nhưng loại thực sự chính xác để sử dụng là ptrdiff_t.
Steven Stewart-Gallus

2
Các câu trả lời không rõ ràng như trong Lập trình cấp thấp: C, Lắp ráp và Thực thi Chương trình trên Intel® 64 . Như đã nêu trong cuốn sách, sử dụng một chỉ mục int icó thể không đủ để giải quyết một mảng lớn. Vì vậy, bằng cách sử dụng, size_t ibạn có thể giải quyết nhiều chỉ số hơn, vì vậy ngay cả khi bạn có một mảng lớn không phải là vấn đề. size_tlà một kiểu dữ liệu: thường là một unsigned long intnhưng điều này phụ thuộc vào hệ thống của bạn.
bruno

Câu trả lời:


461

Từ Wikipedia :

Theo tiêu chuẩn ISO C năm 1999 (C99), size_tlà loại số nguyên không dấu ít nhất 16 bit (xem phần 7.17 và 7.18.3).

size_tlà loại dữ liệu không dấu được xác định bởi một số tiêu chuẩn C / C ++, ví dụ: tiêu chuẩn C99 ISO / IEC 9899, ​​được xác định trong stddef.h. 1 Nó có thể được nhập thêm bằng cách đưa vào stdlib.hvì tập tin này bên trong bao gồm stddef.h.

Loại này được sử dụng để đại diện cho kích thước của một đối tượng. Các hàm thư viện lấy hoặc trả về kích thước mong muốn chúng thuộc loại hoặc có kiểu trả về size_t. Hơn nữa, kích thước toán tử dựa trên trình biên dịch được sử dụng thường xuyên nhất sẽ ước tính giá trị không đổi tương thích với size_t.

Như một hàm ý, size_tlà một loại được đảm bảo để giữ bất kỳ chỉ số mảng.


4
"Các hàm thư viện lấy hoặc trả về kích thước dự kiến ​​chúng có kiểu ... size_t" Ngoại trừ stat () sử dụng off_t cho kích thước của tệp
Draemon

64
@Draemon Nhận xét đó phản ánh một sự nhầm lẫn cơ bản. size_tdành cho các đối tượng trong bộ nhớ. Tiêu chuẩn C thậm chí không định nghĩa stat()hoặc off_t(đó là các định nghĩa POSIX) hoặc bất cứ điều gì để làm với các đĩa hoặc hệ thống tệp - nó tự dừng tại FILEcác luồng. Quản lý bộ nhớ ảo hoàn toàn khác với hệ thống tệp và quản lý tệp theo yêu cầu về kích thước, vì vậy việc đề cập đến off_tlà không liên quan ở đây.
jw013

3
@ jw013: Tôi hầu như không gọi đó là một sự nhầm lẫn cơ bản, nhưng bạn đưa ra một điểm thú vị. Tuy nhiên, văn bản được trích dẫn không nói "kích thước của các đối tượng trong bộ nhớ" và "offset" hầu như không phải là một tên hay cho loại kích thước bất kể nơi nào nó được lưu trữ.
Draemon

30
@Draemon Điểm tốt. Câu trả lời này trích dẫn Wikipedia, trong trường hợp này không có lời giải thích tốt nhất, theo ý kiến ​​của tôi. Bản thân tiêu chuẩn C rõ ràng hơn nhiều: nó định nghĩa size_tlà loại kết quả của sizeoftoán tử (7.17p2 about <stddef.h>). Mục 6.5 giải thích chính xác cách thức biểu thức C hoạt động (6.5.3.4 cho sizeof). Vì bạn không thể áp dụng sizeofcho tệp đĩa (chủ yếu là do C thậm chí không xác định cách thức hoạt động của đĩa và tệp), nên không có chỗ cho sự nhầm lẫn. Nói cách khác, đổ lỗi cho Wikipedia (và câu trả lời này để trích dẫn Wikipedia chứ không phải tiêu chuẩn C thực tế).
jw013

2
@Draemon - Tôi cũng đồng ý với đánh giá "nhầm lẫn cơ bản". Nếu bạn chưa đọc các tiêu chuẩn C / C ++, bạn có thể nghĩ "đối tượng" đề cập đến "lập trình hướng đối tượng", điều này không có. Đọc tiêu chuẩn C, không có đối tượng OOP nào, nhưng chưa có đối tượng và tìm hiểu. Câu trả lời có thế làm bạn ngạc nhiên!
Heath Hunnicutt

220

size_tlà một loại không dấu. Vì vậy, nó không thể đại diện cho bất kỳ giá trị âm (<0). Bạn sử dụng nó khi bạn đang đếm thứ gì đó, và chắc chắn rằng nó không thể âm. Ví dụ, strlen()trả về a size_tvì độ dài của chuỗi phải ít nhất bằng 0.

Trong ví dụ của bạn, nếu chỉ số vòng lặp của bạn sẽ luôn lớn hơn 0, thì có thể có ý nghĩa để sử dụng size_thoặc bất kỳ loại dữ liệu không dấu nào khác.

Khi bạn sử dụng một size_tđối tượng, bạn phải đảm bảo rằng trong tất cả các bối cảnh nó được sử dụng, bao gồm cả số học, bạn muốn các giá trị không âm. Ví dụ: giả sử bạn có:

size_t s1 = strlen(str1);
size_t s2 = strlen(str2);

và bạn muốn tìm sự khác biệt của độ dài str2str1. Bạn không thể làm được:

int diff = s2 - s1; /* bad */

Điều này là do giá trị được gán diffluôn luôn là một số dương, ngay cả khi s2 < s1, bởi vì phép tính được thực hiện với các loại không dấu. Trong trường hợp này, tùy thuộc vào trường hợp sử dụng của bạn, bạn có thể sử dụng int(hoặc long long) cho s1s2.

Có một số chức năng trong C / POSIX có thể / nên sử dụng size_t, nhưng không vì lý do lịch sử. Ví dụ, tham số thứ hai fgetslý tưởng là size_t, nhưng là int.


8
@Alok: Hai câu hỏi: 1) kích thước của là size_tgì? 2) tại sao tôi nên thích size_tcái gì đó như thế unsigned intnào?
Lazer

2
@Lazer: kích thước size_tsizeof(size_t). Tiêu chuẩn C đảm bảo SIZE_MAXsẽ có ít nhất 65535. size_tlà loại được trả về bởi sizeoftoán tử và được sử dụng trong thư viện chuẩn (ví dụ strlentrả về size_t). Như Brendan đã nói, size_tkhông cần phải giống như unsigned int.
Alok Singhal

4
@Lazer - vâng, size_tđược đảm bảo là một loại không dấu.
Alok Singhal

2
@Celeritas không, ý tôi là loại không dấu chỉ có thể biểu thị các giá trị không âm. Tôi có lẽ nên nói "Nó không thể đại diện cho các giá trị âm".
Alok Singhal

4
@JasonOster, bổ sung của hai không phải là một yêu cầu trong tiêu chuẩn C. Nếu giá trị của s2 - s1tràn một int, hành vi không được xác định.
Alok Singhal

73

size_t là một loại có thể giữ bất kỳ chỉ số mảng.

Tùy thuộc vào việc thực hiện, nó có thể là bất kỳ:

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

Đây là cách size_tđịnh nghĩa trong stddef.hmáy của tôi:

typedef unsigned long size_t;

4
Chắc chắn typedef unsigned long size_tlà phụ thuộc vào trình biên dịch. Hay bạn đang đề nghị nó luôn luôn như vậy?
chux - Phục hồi Monica

4
@chux: Thật vậy, chỉ vì một triển khai định nghĩa nó như vậy không có nghĩa là tất cả đều làm. Trường hợp tại điểm: Windows 64 bit. unsigned longlà 32 bit, size_tlà 64 bit.
Tim Čas

2
mục đích của size_t chính xác là gì? Khi tôi có thể tạo một biến cho chính mình như: "int mysize_t;" hoặc "mysize_t dài" hoặc "mysize_t dài không dấu". Tại sao ai đó nên tạo biến này cho tôi?
midkin

1
@midkin size_tkhông phải là một biến. Đây là loại bạn có thể sử dụng khi bạn muốn thể hiện kích thước của một đối tượng trong bộ nhớ.
Arjun Sreedharan

1
Có đúng size_tlà luôn có 32 bit trên máy 32 bit, 64 bit tương tự không?
John Wu

70

Nếu bạn là kiểu người thực nghiệm ,

echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Đầu ra cho Ubuntu 14.04 64-bit GCC 4.8:

typedef long unsigned int size_t;

Lưu ý rằng stddef.hđược cung cấp bởi GCC và không phải glibc src/gcc/ginclude/stddef.htrong GCC 4.2.

Xuất hiện C99 thú vị

  • malloclấy size_tlàm đối số, vì vậy nó xác định kích thước tối đa có thể được phân bổ.

    Và vì nó cũng được trả về sizeof, tôi nghĩ rằng nó giới hạn kích thước tối đa của bất kỳ mảng nào.

    Xem thêm: Kích thước tối đa của một mảng trong C là bao nhiêu?


1
Tôi có cùng môi trường, tuy nhiên, tôi đã kiểm tra 32 bit, vượt qua tùy chọn "-m32" của GCC, kết quả là: "typedef unsign int size_t". Cảm ơn đã chia sẻ lệnh tuyệt vời này @Ciro, nó đã giúp tôi rất nhiều! :-)
silvioprog

2
Vấn đề tự nó không gây nhầm lẫn. Đó là tâm trí khó hiểu cố gắng đặt nhiều câu hỏi, và đưa ra nhiều câu trả lời. Tôi ngạc nhiên khi câu trả lời này và câu trả lời của Arjun Sreedharan vẫn không ngăn được mọi người hỏi và trả lời.
biocyberman

1
Great câu trả lời, bởi vì nó thực sự sẽ cho bạn biết những gì size_t , ít nhất là trên một distro Linux phổ biến.
Andrey Portnoy


19

Vì chưa có ai đề cập đến nó, nên ý nghĩa ngôn ngữ chính size_tsizeoftoán tử trả về một giá trị của loại đó. Tương tự như vậy, ý nghĩa chính của ptrdiff_tviệc trừ đi một con trỏ từ một con trỏ khác sẽ mang lại một giá trị của loại đó. Các hàm thư viện chấp nhận làm như vậy vì nó sẽ cho phép các hàm đó hoạt động với các đối tượng có kích thước vượt quá UINT_MAX trên các hệ thống có thể tồn tại các đối tượng đó, mà không buộc người gọi lãng phí mã truyền qua giá trị lớn hơn "int unsign" trên các hệ thống có loại lớn hơn sẽ đủ cho tất cả các đối tượng có thể.


Câu hỏi của tôi luôn là: Nếu sizeof không bao giờ tồn tại, có cần phải có size_t không?
Trưởng khoa P

@DeanP: Có lẽ là không, mặc dù sau đó sẽ có một câu hỏi về loại đối số nào nên được sử dụng cho những thứ như thế nào malloc(). Cá nhân, tôi muốn thấy các phiên bản có các đối số về loại int, longlong long, với một số triển khai thúc đẩy các loại ngắn hơn và các phiên bản khác triển khai, ví dụ lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}[trên một số nền tảng, gọi imalloc(123)sẽ rẻ hơn gọi lmalloc(123);và thậm chí trên nền tảng size_tlà 16 bit, mã muốn phân bổ kích thước được tính theo giá trị `dài '...
supercat

... Có thể dựa vào phân bổ thất bại nếu giá trị lớn hơn phân bổ có thể xử lý.
supercat

11

Để đi vào lý do tại sao size_tcần phải tồn tại và làm thế nào chúng ta đến đây:

Theo thuật ngữ thực dụng size_tptrdiff_tđược đảm bảo rộng 64 bit khi triển khai 64 bit, rộng 32 bit khi triển khai 32 bit, v.v. Họ không thể ép bất kỳ loại hiện có nào có nghĩa là, trên mọi trình biên dịch, mà không phá vỡ mã kế thừa.

Một size_thoặc ptrdiff_tkhông nhất thiết phải giống như một intptr_thoặc uintptr_t. Chúng khác nhau về các kiến ​​trúc nhất định vẫn còn được sử dụng khi size_tptrdiff_tđược thêm vào Tiêu chuẩn vào cuối những năm 80 và trở nên lỗi thời khi C99 thêm nhiều loại mới nhưng chưa biến mất (như Windows 16 bit). X86 ở chế độ được bảo vệ 16 bit có bộ nhớ được phân đoạn trong đó mảng hoặc cấu trúc lớn nhất có thể có kích thước chỉ 65.536 byte, nhưng farcon trỏ cần phải rộng 32 bit, rộng hơn các thanh ghi. Trên những cái đó, intptr_tsẽ có chiều rộng 32 bit nhưng size_tptrdiff_tcó thể rộng 16 bit và vừa vặn trong một thanh ghi. Và ai biết loại hệ điều hành nào có thể được viết trong tương lai? Về lý thuyết, kiến ​​trúc i386 cung cấp mô hình phân đoạn 32 bit với các con trỏ 48 bit mà chưa có hệ điều hành nào thực sự sử dụng.

Loại bù bộ nhớ không thể là longdo có quá nhiều mã kế thừa giả định longchính xác là rộng 32 bit. Giả định này thậm chí đã được tích hợp vào API UNIX và Windows. Thật không may, rất nhiều mã kế thừa khác cũng cho rằng một mã longđủ rộng để chứa một con trỏ, một tệp bù, số giây đã trôi qua kể từ năm 1970, v.v. POSIX hiện cung cấp một cách tiêu chuẩn hóa để buộc giả định sau là đúng thay vì giả định trước, nhưng cũng không phải là giả định di động để thực hiện.

Không thể nào intvì chỉ một số ít trình biên dịch trong thập niên 90 tạo ra int64 bit. Sau đó, họ thực sự trở nên kỳ lạ bằng cách giữ long32 bit rộng. Bản sửa đổi tiếp theo của Tiêu chuẩn tuyên bố nó bất hợp pháp vì intrộng hơn long, nhưng intvẫn rộng 32 bit trên hầu hết các hệ thống 64 bit.

Không thể long long int, dù sao nó đã được thêm vào sau đó, vì nó được tạo ra có chiều rộng tối thiểu 64 bit ngay cả trên các hệ thống 32 bit.

Vì vậy, một loại mới là cần thiết. Ngay cả khi nó không, tất cả các loại khác có nghĩa là một cái gì đó khác với phần bù trong một mảng hoặc đối tượng. Và nếu có một bài học từ sự chuyển đổi từ 32 đến 64 bit, thì đó là cụ thể về các thuộc tính mà một loại cần có, và không sử dụng một thuộc tính có nghĩa là những thứ khác nhau trong các chương trình khác nhau.


Không đồng ý với " size_tptrdiff_tđược đảm bảo rộng 64 bit khi triển khai 64 bit", v.v ... Bảo đảm bị cường điệu hóa. Phạm vi size_tchủ yếu được điều khiển bởi dung lượng bộ nhớ của việc thực hiện. "triển khai n-bit" chủ yếu là độ rộng bộ xử lý riêng của số nguyên. Chắc chắn nhiều triển khai sử dụng bộ nhớ kích thước bộ nhớ và độ rộng bus bộ xử lý tương tự, nhưng số nguyên gốc rộng với bộ nhớ ít hoặc bộ xử lý hẹp có nhiều bộ nhớ tồn tại và làm tách rời hai thuộc tính thực hiện này.
chux - Phục hồi lại

8

size_tintkhông thể thay thế cho nhau. Chẳng hạn, trên Linux size_t64 bit có kích thước 64 bit (tức là sizeof(void*)) nhưng intlà 32 bit.

Cũng lưu ý rằng size_tkhông dấu. Nếu bạn cần phiên bản đã ký thì có ssize_ttrên một số nền tảng và nó sẽ phù hợp hơn với ví dụ của bạn.

Như một quy tắc chung, tôi sẽ đề nghị sử dụng intcho hầu hết các trường hợp chung và chỉ sử dụng size_t/ ssize_tkhi có nhu cầu cụ thể cho nó ( mmap()ví dụ như).


3

Nói chung, nếu bạn đang bắt đầu từ 0 và đi lên, hãy luôn sử dụng loại không dấu để tránh tràn vào tình huống giá trị âm. Điều này cực kỳ quan trọng, bởi vì nếu giới hạn mảng của bạn nhỏ hơn mức tối đa của vòng lặp của bạn, nhưng mức tối đa của vòng lặp xảy ra lớn hơn mức tối đa của loại của bạn, bạn sẽ bao quanh âm và bạn có thể gặp lỗi phân đoạn (SIGSEGV ). Vì vậy, nói chung, không bao giờ sử dụng int cho một vòng lặp bắt đầu từ 0 và đi lên. Sử dụng một dấu.


3
Tôi không thể chấp nhận lập luận của bạn. Bạn nói rằng tốt hơn là lỗi tràn âm thầm dẫn đến việc truy cập dữ liệu hợp lệ trong mảng của bạn?
maf-soft

1
@ maf-mềm là chính xác. nếu lỗi không bị phát hiện, nó làm cho nó tồi tệ hơn một sự cố chương trình. Tại sao câu trả lời này có upvotes?
yoyo_fun

Nếu nó truy cập dữ liệu hợp lệ trong mảng của bạn thì đó không phải là lỗi vì loại không dấu sẽ không tràn ở giới hạn loại đã ký. Những kẻ logic này là gì? Giả sử vì một số lý do bạn sử dụng char để lặp lại hơn 256 phần tử ... đã ký sẽ tràn ở phần tử 127 và 128 sẽ sigsegv, nhưng nếu bạn sử dụng không dấu, thì nó sẽ đi qua toàn bộ mảng như dự định. Sau đó, một lần nữa, khi bạn đang sử dụng một int, mảng của bạn sẽ không thực sự lớn hơn 2 tỷ phần tử vì vậy dù thế nào cũng không thành vấn đề ...
Purple Ice

1
Tôi không thể tưởng tượng bất kỳ tình huống nào trong đó tràn số nguyên không phải là một lỗi, cho dù nó bao quanh tích cực hay tiêu cực. Chỉ vì bạn không nhận được segfault không có nghĩa là bạn thấy hành vi đúng! Và bạn có thể gặp lỗi phân đoạn hoặc không, cho dù phần bù của bạn là dương hay âm; tất cả phụ thuộc vào bố cục bộ nhớ của bạn. @PurpleIce, tôi không nghĩ bạn đang nói điều tương tự như câu trả lời này; đối số của bạn có vẻ là bạn nên chọn một kiểu dữ liệu đủ lớn để giữ giá trị lớn nhất bạn muốn đặt vào đó, đó chỉ là lẽ thường.
Soren Bjornstad

Điều đó nói rằng, tôi thích sử dụng một loại không dấu cho các chỉ số vòng lặp về mặt ngữ nghĩa ; nếu biến của bạn sẽ không bao giờ âm, thì bạn cũng có thể chỉ ra rằng trong loại bạn chọn. Nó cũng có thể cho phép trình biên dịch phát hiện ra một lỗi trong đó giá trị kết thúc âm, mặc dù GCC ít nhất là khá khủng khiếp khi phát hiện ra lỗi cụ thể này (trong một lần tôi đã khởi tạo một dấu không dấu thành -1 và không nhận được cảnh báo). Tương tự, size_t phù hợp về mặt ngữ nghĩa cho các chỉ số mảng.
Soren Bjornstad

3

size_t là kiểu dữ liệu số nguyên không dấu. Trên các hệ thống sử dụng Thư viện GNU C, đây sẽ là int int hoặc unsign dài int. size_t thường được sử dụng để lập chỉ mục mảng và đếm vòng lặp.


1

size_t hoặc bất kỳ loại không dấu nào có thể được nhìn thấy được sử dụng làm biến vòng lặp vì các biến vòng lặp thường lớn hơn hoặc bằng 0.

Khi chúng tôi sử dụng một đối tượng size_t , chúng tôi phải đảm bảo rằng trong tất cả các bối cảnh nó được sử dụng, bao gồm cả số học, chúng tôi chỉ muốn các giá trị không âm. Chẳng hạn, chương trình sau đây chắc chắn sẽ cho kết quả bất ngờ:

// C program to demonstrate that size_t or
// any unsigned int type should be used 
// carefully when used in a loop

#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];

// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;

// But reverse cycles are tricky for unsigned 
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}

Output
Infinite loop and then segmentation fault

1

size_tlà kiểu dữ liệu số nguyên không dấu, chỉ có thể gán 0 và lớn hơn 0 giá trị nguyên. Nó đo các byte có kích thước của bất kỳ đối tượng nào và được trả về bởi sizeoftoán tử. constlà đại diện cú pháp của size_t, nhưng không có constbạn có thể chạy chương trình.

const size_t number;

size_tthường xuyên được sử dụng để lập chỉ mục mảng và đếm vòng lặp. Nếu trình biên dịch là 32-bitnó sẽ làm việc trên unsigned int. Nếu trình biên dịch là 64-bitnó sẽ làm việc trên unsigned long long int. Có kích thước tối đa size_ttùy thuộc vào loại trình biên dịch.

size_tđã xác định trên <stdio.h>tập tin tiêu đề, nhưng Nó cũng có thể xác định bằng cách <stddef.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>tiêu đề.

  • Ví dụ (với const)
#include <stdio.h>

int main()
{
    const size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Đầu ra -: size = 800


  • Ví dụ (không có const)
#include <stdio.h>

int main()
{
    size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Đầu ra -: size = 800


-3

Theo hiểu biết của tôi, size_tlà một unsignedsố nguyên có kích thước bit đủ lớn để giữ một con trỏ của kiến ​​trúc gốc.

Vì thế:

sizeof(size_t) >= sizeof(void*)

16
Không đúng. Kích thước con trỏ có thể lớn hơn size_t. Một số ví dụ: Trình biên dịch C trên chế độ thực x86 có thể có 32 bit FARhoặc HUGEcon trỏ nhưng size_t vẫn là 16 bit. Một ví dụ khác: Watcom C từng có một con trỏ chất béo đặc biệt cho bộ nhớ mở rộng rộng 48 bit, nhưng size_tkhông. Trên bộ điều khiển nhúng với kiến ​​trúc Harvard, bạn cũng không có mối tương quan nào, bởi vì cả hai đều liên quan đến các không gian địa chỉ khác nhau.
Patrick Schlüter

1
Và trên stackoverflow.com/questions/1572099/, có nhiều ví dụ AS / 400 với con trỏ 128 bit và 32 bitsize_t
Patrick Schlüter

Điều này là sai lầm trắng trợn. Tuy nhiên, hãy giữ nó ở đây
Antti Haapala
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.