size_t so với uintptr_t


246

Tiêu chuẩn C đảm bảo rằng đó size_tlà một loại có thể chứa bất kỳ chỉ số mảng nào. Điều này có nghĩa là, về mặt logic, size_tsẽ có thể giữ bất kỳ loại con trỏ nào. Tôi đã đọc trên một số trang web mà tôi tìm thấy trên Google rằng đây là hợp pháp và / hoặc sẽ luôn hoạt động:

void *v = malloc(10);
size_t s = (size_t) v;

Vì vậy, trong C99, tiêu chuẩn đã giới thiệu các loại intptr_tuintptr_tloại, được ký và loại không dấu được đảm bảo để có thể giữ con trỏ:

uintptr_t p = (size_t) v;

Vậy sự khác biệt giữa sử dụng size_tvà là uintptr_tgì? Cả hai đều không dấu và cả hai sẽ có thể giữ bất kỳ loại con trỏ nào, vì vậy chúng có vẻ giống nhau về mặt chức năng. Có bất kỳ lý do thuyết phục thực sự để sử dụng uintptr_t(hoặc tốt hơn, a void *) chứ không phải là size_t, ngoài sự rõ ràng? Trong một cấu trúc mờ, trong đó trường sẽ chỉ được xử lý bởi các hàm bên trong, có lý do gì để không làm điều này không?

Với cùng một mã thông báo, ptrdiff_tđã là một loại được ký có khả năng giữ sự khác biệt của con trỏ và do đó có khả năng giữ hầu hết mọi con trỏ, vậy nó khác với intptr_tnhư thế nào?

Không phải tất cả các loại này về cơ bản đều phục vụ các phiên bản khác nhau của cùng một chức năng sao? Nếu không, tại sao? Tôi không thể làm gì với một trong số họ mà tôi không thể làm với người khác? Nếu vậy, tại sao C99 lại thêm hai loại cơ bản không cần thiết vào ngôn ngữ?

Tôi sẵn sàng bỏ qua các con trỏ hàm, vì chúng không áp dụng cho vấn đề hiện tại, nhưng cứ thoải mái đề cập đến chúng, vì tôi có một nghi ngờ lén lút chúng sẽ là trung tâm của câu trả lời "chính xác".

Câu trả lời:


236

size_tlà một loại có thể giữ bất kỳ chỉ số mảng. Điều này có nghĩa là, về mặt logic, size_t sẽ có thể giữ bất kỳ loại con trỏ nào

Không cần thiết! Ví dụ, quay trở lại thời của kiến ​​trúc 16 bit được phân đoạn: ví dụ: một mảng có thể bị giới hạn ở một phân đoạn duy nhất (vì vậy 16 bit size_tsẽ làm được) NHƯNG bạn có thể có nhiều phân đoạn (vì vậy intptr_tcần phải chọn loại 32 bit phân khúc cũng như sự bù đắp bên trong nó). Tôi biết những điều này nghe có vẻ kỳ lạ trong những ngày của các kiến ​​trúc không phân chia địa chỉ thống nhất, nhưng tiêu chuẩn PHẢI phục vụ cho nhiều loại hơn so với "những gì bình thường trong năm 2009", bạn biết đấy! -)


6
Điều này, cùng với rất nhiều những người khác nhảy đến kết luận tương tự, giải thích sự khác biệt giữa size_tuintptr_tnhưng những gì về ptrdiff_tintptr_t- sẽ không được cả hai trong số này có thể lưu trữ cùng một phạm vi giá trị trên nền tảng hầu như bất kỳ? Tại sao có cả hai loại số nguyên có kích thước con trỏ được ký và không dấu, đặc biệt nếu ptrdiff_tđã phục vụ mục đích của loại số nguyên có kích thước con trỏ đã ký.
Chris Lutz

8
Cụm từ chính có "trên hầu hết mọi nền tảng", @Chris. Việc triển khai là miễn phí để giới hạn các con trỏ trong phạm vi 0xf000-0xffff - điều này đòi hỏi một intptr_t 16 bit nhưng chỉ một ptrdiff_t 12/13 bit.
paxdiablo

29
@Chris, chỉ đối với các con trỏ bên trong cùng một mảng thì nó được xác định rõ để có sự khác biệt của chúng. Vì vậy, trên các kiến ​​trúc 16 bit giống hệt nhau (mảng phải nằm trong một phân đoạn duy nhất nhưng hai mảng khác nhau có thể ở các phân đoạn khác nhau) con trỏ phải là 4 byte nhưng khác biệt con trỏ có thể là 2 byte!
Alex Martelli

6
@AlexMartelli: Ngoại trừ sự khác biệt con trỏ có thể là tích cực hoặc tiêu cực. Tiêu chuẩn yêu cầu size_tphải có ít nhất 16 bit, nhưng ptrdiff_tít nhất là 17 bit (trong thực tế có nghĩa là nó có thể sẽ có ít nhất 32 bit).
Keith Thompson

3
Kiến trúc phân khúc Nevermind, còn một kiến ​​trúc hiện đại như x86-64 thì sao? Việc triển khai sớm kiến ​​trúc này chỉ cung cấp cho bạn một không gian địa chỉ 48 bit, nhưng bản thân các con trỏ là kiểu dữ liệu 64 bit. Khối bộ nhớ liền kề lớn nhất mà bạn có thể giải quyết một cách hợp lý sẽ là 48 bit, vì vậy tôi phải tưởng tượng SIZE_MAXkhông nên là 2 ** 64. Đây là sử dụng địa chỉ phẳng, nhớ bạn; không cần phân đoạn để có sự không khớp giữa SIZE_MAXvà phạm vi của một con trỏ dữ liệu.
Andon M. Coleman

89

Về tuyên bố của bạn:

"Tiêu chuẩn C đảm bảo rằng đó size_tlà một loại có thể chứa bất kỳ chỉ số mảng nào. Điều này có nghĩa là, về mặt logic, size_tsẽ có thể giữ bất kỳ loại con trỏ nào."

Đây thực sự là một ngụy biện (một quan niệm sai lầm do suy luận không chính xác) (a) . Bạn có thể nghĩ cái sau theo cái trước nhưng thực tế không phải vậy.

Con trỏ và chỉ mục mảng không giống nhau. Thật hợp lý khi dự tính một triển khai tuân thủ giới hạn các mảng ở 65536 phần tử nhưng cho phép các con trỏ giải quyết bất kỳ giá trị nào trong một không gian địa chỉ 128 bit lớn.

C99 nói rằng giới hạn trên của một size_tbiến được xác định bởi SIZE_MAXvà giá trị này có thể thấp đến 65535 (xem C99 TR3, 7.18.3, không thay đổi trong C11). Con trỏ sẽ khá hạn chế nếu chúng bị giới hạn trong phạm vi này trong các hệ thống hiện đại.

Trong thực tế, có thể bạn sẽ thấy rằng giả định của mình đúng, nhưng đó không phải là vì tiêu chuẩn đảm bảo nó. Bởi vì nó thực sự không đảm bảo nó.


(a) đây không phải là một hình thức tấn công cá nhân, chỉ nêu lý do tại sao các phát biểu của bạn sai lầm trong bối cảnh suy nghĩ phê phán. Ví dụ: lý do sau đây cũng không hợp lệ:

Tất cả chó con đều dễ thương. Điều này thật dễ thương. Vì vậy, điều này phải là một con chó con.

Sự dễ thương hay nói cách khác của con rối không có ý nghĩa ở đây, tất cả tôi nói rằng hai sự thật không dẫn đến kết luận, bởi vì hai câu đầu tiên cho phép tồn tại những thứ dễ thương không phải là chó con.

Điều này tương tự như tuyên bố đầu tiên của bạn không nhất thiết bắt buộc thứ hai.


Thay vì gõ lại những gì tôi đã nói trong các bình luận cho Alex Martelli, tôi sẽ chỉ nói cảm ơn vì đã làm rõ, nhưng nhắc lại nửa sau câu hỏi của tôi (phần ptrdiff_tso với intptr_tphần).
Chris Lutz

5
@Ivan, như với hầu hết các giao tiếp, cần có sự hiểu biết chung về các mục cơ bản nhất định. Nếu bạn thấy câu trả lời này là "chọc cho vui", tôi đảm bảo với bạn rằng đó là sự hiểu lầm về ý định của tôi. Giả sử rằng bạn đang đề cập đến nhận xét 'ngụy biện logic' của tôi (tôi không thể thấy bất kỳ khả năng nào khác), đó có nghĩa là một tuyên bố thực tế, không phải là một số tuyên bố được thực hiện với chi phí của OP. Nếu bạn muốn đề xuất một số cải tiến cụ thể để giảm thiểu khả năng hiểu lầm (thay vì chỉ là một khiếu nại chung), tôi rất vui lòng xem xét.
paxdiablo

1
@ivan_pozdeev - đó là một cặp chỉnh sửa đáng ghét và quyết liệt, và tôi không thấy bằng chứng nào cho thấy paxdiablo đã "chọc cười" bất cứ ai. Nếu tôi là OP, tôi sẽ quay lại ngay ....
ex nihilo

1
@Ivan, không thực sự hài lòng với các chỉnh sửa mà bạn đề xuất, đã quay lại và cũng cố gắng loại bỏ bất kỳ hành vi phạm tội ngoài ý muốn. Nếu bạn có bất kỳ thay đổi nào khác để cung cấp, tôi khuyên bạn nên bắt đầu một cuộc trò chuyện để chúng ta có thể thảo luận.
paxdiablo

1
@paxdiablo được rồi, tôi đoán "đây thực sự là một lời ngụy biện" ít bảo trợ hơn.
ivan_pozdeev

36

Tôi sẽ để tất cả các câu trả lời khác tự đứng ra liên quan đến lý do với các giới hạn phân khúc, kiến ​​trúc kỳ lạ, v.v.

Không phải là sự khác biệt đơn giản trong tên đủ lý do để sử dụng loại thích hợp cho điều đúng?

Nếu bạn đang lưu trữ một kích thước, sử dụng size_t. Nếu bạn đang lưu trữ một con trỏ, sử dụng intptr_t. Một người đọc mã của bạn sẽ biết ngay rằng "aha, đây là kích thước của một thứ gì đó, có thể tính bằng byte" và "oh, đây là một giá trị con trỏ đang được lưu trữ dưới dạng số nguyên, vì một số lý do".

Mặt khác, bạn chỉ có thể sử dụng unsigned long(hoặc, trong những thời điểm hiện đại ở đây unsigned long long) cho mọi thứ. Kích thước không phải là tất cả, tên loại mang ý nghĩa hữu ích vì nó giúp mô tả chương trình.


Tôi đồng ý, nhưng tôi đang xem xét một cái gì đó của hack / trick (tất nhiên là tôi sẽ ghi lại rõ ràng) liên quan đến việc lưu trữ một loại con trỏ trong một size_ttrường.
Chris Lutz

@MarkAdler Standard không yêu cầu con trỏ phải được biểu diễn dưới dạng số nguyên hoàn toàn: Bất kỳ loại con trỏ nào cũng có thể được chuyển đổi thành loại số nguyên. Trừ khi được chỉ định trước đó, kết quả được xác định theo thực hiện. Nếu kết quả không thể được biểu diễn trong kiểu số nguyên, hành vi không được xác định. Kết quả không cần phải nằm trong phạm vi giá trị của bất kỳ loại số nguyên nào. Như vậy, chỉ void*, intptr_tuintptr_tđược đảm bảo để có thể đại diện cho bất kỳ con trỏ đến dữ liệu.
Andrew Svietlichnyy

12

Có thể kích thước của mảng lớn nhất nhỏ hơn một con trỏ. Hãy nghĩ về kiến ​​trúc được phân đoạn - con trỏ có thể là 32 bit, nhưng một phân đoạn duy nhất có thể chỉ giải quyết được 64KB (ví dụ kiến ​​trúc 8086 chế độ thực cũ).

Mặc dù chúng không còn được sử dụng trong các máy tính để bàn nữa, tiêu chuẩn C được dự định để hỗ trợ các kiến ​​trúc nhỏ, chuyên dụng. Vẫn có những hệ thống nhúng đang được phát triển với CPU 8 hoặc 16 bit chẳng hạn.


Nhưng bạn có thể lập chỉ mục con trỏ giống như mảng, vì vậy size_tcũng có thể xử lý điều đó? Hoặc các mảng động trong một số phân khúc xa vẫn bị giới hạn trong việc lập chỉ mục trong phân khúc của chúng?
Chris Lutz

Con trỏ chỉ mục chỉ được hỗ trợ về mặt kỹ thuật đối với kích thước của mảng mà chúng trỏ tới - vì vậy nếu một mảng bị giới hạn ở kích thước 64KB, đó là tất cả những gì con số cần phải hỗ trợ. Tuy nhiên, các trình biên dịch MS-DOS đã hỗ trợ mô hình bộ nhớ 'khổng lồ', trong đó các con trỏ xa (các con trỏ được phân đoạn 32 bit) được thao tác để chúng có thể xử lý toàn bộ bộ nhớ dưới dạng một mảng duy nhất - nhưng việc xử lý đối với các con trỏ phía sau hậu trường là khá xấu xí - khi phần bù tăng lên vượt quá giá trị 16 (hoặc thứ gì đó), phần bù được gói lại thành 0 và phần phân đoạn được tăng lên.
Michael Burr

7
Đọc en.wikipedia.org/wiki/C_memory_model#Memory_seributionation và khóc cho các lập trình viên MS-DOS đã chết để chúng tôi có thể tự do.
Justicle

Tệ hơn nữa là chức năng stdlib không quan tâm đến từ khóa HUGE. 16bit MS-C cho tất cả các strchức năng và Borland ngay cả đối với các memchức năng ( memset, memcpy, memmove). Điều này có nghĩa là bạn có thể ghi đè lên một phần bộ nhớ khi phần bù bị tràn, thật thú vị khi gỡ lỗi trên nền tảng nhúng của chúng tôi.
Patrick Schlüter

@Justicle: Kiến trúc phân đoạn 8086 không được hỗ trợ tốt trong C, nhưng tôi biết không có kiến ​​trúc nào khác hiệu quả hơn trong trường hợp không gian địa chỉ 1MB là đủ nhưng không phải là 64K. Một số JVM hiện đại thực sự sử dụng địa chỉ rất giống chế độ thực x86, sử dụng các tham chiếu đối tượng 32 bit còn lại 3 bit để tạo địa chỉ cơ sở đối tượng trong không gian địa chỉ 32 GB.
supercat

5

Tôi sẽ tưởng tượng (và điều này đúng với tất cả các tên loại) rằng nó truyền đạt tốt hơn ý định của bạn trong mã.

Ví dụ, mặc dù unsigned shortwchar_tcó cùng kích thước trên Windows (tôi nghĩ), sử dụng wchar_tthay vì unsigned shortthể hiện ý định rằng bạn sẽ sử dụng nó để lưu trữ một ký tự rộng, thay vì chỉ một số tùy ý.


Nhưng có một sự khác biệt ở đây - trên hệ thống của tôi, wchar_tlớn hơn nhiều so với việc unsigned shortsử dụng cái này cho cái kia sẽ là sai lầm và tạo ra mối quan tâm về tính di động nghiêm trọng (và hiện đại), trong khi mối quan tâm về tính di động giữa size_tuintptr_tdường như nằm ở vùng đất xa xôi của năm 1980-một cái gì đó (đâm ngẫu nhiên trong bóng tối vào ngày, ở đó)
Chris Lutz

Cảm động! Nhưng sau đó một lần nữa, size_tuintptr_tvẫn có ngụ ý sử dụng trong tên của họ.
mơ mộng

Họ làm, và tôi muốn biết liệu có một động lực cho điều này ngoài sự rõ ràng đơn giản. Và hóa ra là có.
Chris Lutz

3

Nhìn cả về phía trước và phía trước, và nhớ lại rằng các kiến ​​trúc kỳ quặc khác nhau nằm rải rác về cảnh quan, tôi khá chắc chắn rằng họ đang cố gắng bọc tất cả các hệ thống hiện có và cũng cung cấp cho tất cả các hệ thống có thể trong tương lai.

Vì vậy, chắc chắn, cách mọi thứ giải quyết, cho đến nay chúng ta cần không quá nhiều loại.

Nhưng ngay cả trong LP64, một mô hình khá phổ biến, chúng tôi cần size_t và ssize_t cho giao diện gọi hệ thống. Người ta có thể tưởng tượng một hệ thống tương lai hoặc hệ thống tương lai bị ràng buộc nhiều hơn, trong đó việc sử dụng loại 64 bit đầy đủ là tốn kém và họ có thể muốn sử dụng các op I / O lớn hơn 4GB nhưng vẫn có con trỏ 64 bit.

Tôi nghĩ bạn phải tự hỏi: những gì có thể đã được phát triển, những gì có thể đến trong tương lai. (Có lẽ các con trỏ trên toàn hệ thống phân tán 128 bit, nhưng không quá 64 bit trong một cuộc gọi hệ thống, hoặc thậm chí là giới hạn 32 "di sản". :-) Hình ảnh rằng các hệ thống cũ có thể có trình biên dịch C mới .. .

Ngoài ra, hãy nhìn vào những gì tồn tại xung quanh sau đó. Bên cạnh các mô hình bộ nhớ chế độ thực zillion 286, còn các khung hình chính của con trỏ CDC 60 bit / con trỏ 18 bit thì sao? Làm thế nào về loạt Cray? Đừng bận tâm ILP64, LP64, LLP64 bình thường. (Tôi luôn nghĩ microsoft rất tự phụ với LLP64, đáng ra nó phải là P64.) Tôi chắc chắn có thể tưởng tượng một ủy ban đang cố gắng bao gồm tất cả các cơ sở ...


-9
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

Ngụ ý rằng intptr_t phải luôn thay thế cho size_t và ngược lại.


10
Tất cả những gì hiển thị là một cú pháp cú pháp cụ thể của C. Lập chỉ mục mảng được xác định theo x [y] tương đương với * (x + y) và vì a + 3 và 3 + a giống hệt nhau về loại và giá trị, bạn có thể sử dụng 3 [a] hoặc [3].
Fred Nurk
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.