Liệu một lệnh cấm dài có ý nghĩa?


109

Trong thế giới đa nền tảng C ++ (hoặc C) ngày nay, chúng ta :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

Điều này có nghĩa là ngày nay, đối với bất kỳ số nguyên "chung" (đã ký) nào, intsẽ đủ và vẫn có thể được sử dụng làm kiểu số nguyên mặc định khi viết mã ứng dụng C ++. Nó cũng sẽ - cho các mục đích thực tế hiện tại - có kích thước phù hợp trên các nền tảng.

Nếu một trường hợp sử dụng cần ít nhất 64 bit, ngày nay chúng ta có thể sử dụng long long, mặc dù có thể sử dụng một trong các loại chỉ định bitness hoặc __int64loại có thể có ý nghĩa hơn.

Điều này longnằm ở giữa và chúng tôi đang xem xét hoàn toàn cấm sử longdụng mã ứng dụng của chúng tôi .

Điều này sẽ có ý nghĩa , hoặc có trường hợp sử dụng longmã C ++ (hoặc C) hiện đại phải chạy đa nền tảng không? (nền tảng là máy tính để bàn, thiết bị di động, nhưng không phải là những thứ như vi điều khiển, DSP, v.v.)


Có thể liên kết nền thú vị:


14
Làm thế nào bạn sẽ đối phó với các cuộc gọi đến thư viện sử dụng lâu dài?
Ángel

14
longlà cách duy nhất để đảm bảo 32 bit. intcó thể là 16 bit nên đối với một số ứng dụng thì không đủ. Có, intđôi khi là 16 bit trên trình biên dịch hiện đại. Vâng, mọi người viết phần mềm trên vi điều khiển. Tôi cho rằng nhiều người viết phần mềm có nhiều người dùng trên vi điều khiển hơn trên PC với sự gia tăng của các thiết bị iPhone và Android, chưa kể đến sự gia tăng của Arduinos, v.v.
slebetman

53
Tại sao không cấm char, short, int, long và long long và sử dụng các loại [u] intXX_t?
Immibis

7
@slebetman Tôi đã đào sâu hơn một chút, có vẻ như yêu cầu vẫn còn, mặc dù ẩn trong §3.9.1.3 trong đó tiêu chuẩn C ++ nêu rõ: "Các kiểu số nguyên đã ký và không dấu sẽ thỏa mãn các ràng buộc được đưa ra trong tiêu chuẩn C, mục 5.2. 4.2.1. " Và trong tiêu chuẩn C §5.2.4.2.1, nó nêu phạm vi tối thiểu, chính xác như bạn đã viết. Bạn đã hoàn toàn đúng. :) Rõ ràng việc sở hữu một bản sao của tiêu chuẩn C ++, là không đủ, người ta cũng cần tìm bản sao của tiêu chuẩn C.
Tommy Andersen

11
Bạn đang thiếu thế giới DOSBox / Turbo C ++, intvẫn còn rất nhiều 16 bit. Tôi ghét phải nói điều đó, nhưng nếu bạn sẽ viết về "thế giới đa nền tảng ngày nay", bạn không thể bỏ qua toàn bộ tiểu lục địa Ấn Độ.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Câu trả lời:


17

Lý do duy nhất tôi sẽ sử dụng longngày hôm nay là khi gọi hoặc thực hiện giao diện bên ngoài sử dụng nó.

Như bạn nói trong bài đăng của bạn ngắn và int có các đặc điểm khá ổn định trên tất cả các nền tảng máy tính để bàn / máy chủ / di động lớn hiện nay và tôi thấy không có lý do gì để thay đổi trong tương lai gần. Vì vậy, tôi thấy ít lý do để tránh chúng nói chung.

longmặt khác là một mớ hỗn độn. Trên tất cả các hệ thống 32 bit, tôi biết rằng nó có các đặc điểm sau.

  1. Nó có kích thước chính xác là 32 bit.
  2. Nó có cùng kích thước với một địa chỉ bộ nhớ.
  3. Nó có cùng kích thước với đơn vị dữ liệu lớn nhất có thể được giữ trong một thanh ghi bình thường và hoạt động với một lệnh duy nhất.

Một lượng lớn mã được viết dựa trên một hoặc nhiều đặc điểm này. Tuy nhiên, với việc chuyển sang 64-bit, không thể bảo toàn tất cả chúng. Các nền tảng giống như Unix đã sử dụng LP64, bảo tồn các đặc tính 2 và 3 với chi phí đặc trưng 1. Win64 đã sử dụng LLP64, bảo tồn đặc tính 1 với chi phí của các đặc điểm 2 và 3. Kết quả là bạn không còn có thể dựa vào bất kỳ đặc điểm nào trong số các đặc điểm đó và IMO để lại ít lý do để sử dụng long.

Nếu bạn muốn một loại có kích thước chính xác 32 bit, bạn nên sử dụng int32_t.

Nếu bạn muốn một loại có cùng kích thước với một con trỏ bạn nên sử dụng intptr_t(hoặc tốt hơn uintptr_t).

Nếu bạn muốn một loại là mục lớn nhất có thể được thực hiện trong một đăng ký / hướng dẫn thì thật không may, tôi không nghĩ rằng tiêu chuẩn cung cấp một loại. size_tphải đúng trên hầu hết các nền tảng phổ biến nhưng nó sẽ không có trên x32 .


PS

Tôi sẽ không bận tâm với các loại "nhanh" hoặc "ít nhất". Các loại "ít nhất" chỉ quan trọng nếu bạn quan tâm đến tính di động đối với các kiến ​​trúc thực sự tối nghĩa ở đâu CHAR_BIT != 8. Kích thước của các loại "nhanh" trong thực tế dường như khá đơn giản. Linux dường như làm cho chúng ít nhất có cùng kích thước với con trỏ, điều này thật ngớ ngẩn trên các nền tảng 64 bit có hỗ trợ 32 bit nhanh như x86-64 và arm64. IIRC iOS làm cho chúng nhỏ nhất có thể. Tôi không chắc những hệ thống khác làm gì.


PPS

Một lý do để sử dụng unsigned long(nhưng không đơn giản long) là bởi vì nó được đảm bảo có hành vi modulo. Thật không may do các quy tắc quảng cáo sai lầm của C loại không dấu nhỏ hơn intkhông có hành vi modulo.

Trên tất cả các nền tảng chính hiện nay uint32_tcó cùng kích thước hoặc lớn hơn int và do đó có hành vi modulo. Tuy nhiên, đã có lịch sử và về mặt lý thuyết có thể có trong các nền tảng tương lai với int64-bit và do đó uint32_tkhông có hành vi modulo.

Cá nhân tôi sẽ nói rằng tốt hơn là nên tham gia vào việc ép buộc hành vi modulo bằng cách sử dụng "1u *" hoặc "0u +" khi bắt đầu phương trình của bạn vì điều này sẽ hoạt động với mọi kích thước của loại không dấu.


1
Tất cả các loại "kích thước được chỉ định" sẽ hữu ích hơn nhiều nếu chúng có thể chỉ định ngữ nghĩa khác với các loại tích hợp. Ví dụ, sẽ rất hữu ích khi có một loại sử dụng số học mod-65536 bất kể kích thước của "int", cùng với loại có khả năng giữ các số từ 0 đến 65535 nhưng có thể tùy ý và không nhất thiết có khả năng nhất quán nắm giữ số lượng lớn hơn đó. Loại kích thước nào là nhanh nhất sẽ có trên hầu hết các máy phụ thuộc vào ngữ cảnh, vì vậy việc có thể để trình biên dịch chọn tùy ý sẽ là tối ưu cho tốc độ.
supercat

204

Như bạn đã đề cập trong câu hỏi của mình, phần mềm hiện đại là tất cả về sự tương tác giữa các nền tảng và hệ thống trên internet. Các tiêu chuẩn C và C ++ đưa ra các phạm vi cho kích thước loại số nguyên, không phải kích thước cụ thể (trái ngược với các ngôn ngữ như Java và C #).

Để đảm bảo phần mềm của bạn được biên dịch trên các nền tảng khác nhau hoạt động với cùng một dữ liệu theo cùng một cách để đảm bảo rằng phần mềm khác có thể tương tác với phần mềm của bạn bằng cùng kích thước, bạn nên sử dụng các số nguyên có kích thước cố định.

Enter <cstdint>cung cấp chính xác điều đó và là một tiêu đề tiêu chuẩn mà tất cả các nền tảng thư viện và thư viện chuẩn được yêu cầu cung cấp. Lưu ý: tiêu đề này chỉ được yêu cầu kể từ C ++ 11, nhưng nhiều triển khai thư viện cũ hơn vẫn cung cấp nó.

Muốn có một số nguyên không dấu 64 bit? Sử dụng uint64_t. Ký số nguyên 32 bit? Sử dụng int32_t. Mặc dù các loại trong tiêu đề là tùy chọn, các nền tảng hiện đại sẽ hỗ trợ tất cả các loại được xác định trong tiêu đề đó.

Đôi khi, cần một độ rộng bit cụ thể, ví dụ, trong cấu trúc dữ liệu được sử dụng để liên lạc với các hệ thống khác. Lần khác thì không. Đối với các tình huống ít nghiêm ngặt hơn, <cstdint>cung cấp các loại có chiều rộng tối thiểu.

ít biến thể nhất : int_leastXX_tsẽ là một kiểu số nguyên của các bit XX tối thiểu. Nó sẽ sử dụng loại nhỏ nhất cung cấp các bit XX, nhưng loại được phép lớn hơn số bit được chỉ định. Trong thực tế, chúng thường giống như các loại được mô tả ở trên cung cấp số bit chính xác.

Ngoài ra còn có các biến thể nhanh : int_fastXX_tít nhất là XX bit, nhưng nên sử dụng một loại hoạt động nhanh trên một nền tảng cụ thể. Định nghĩa "nhanh" trong bối cảnh này là không xác định. Tuy nhiên, trong thực tế, điều này thường có nghĩa là một loại nhỏ hơn kích thước thanh ghi của CPU có thể bí danh với một loại kích thước thanh ghi của CPU. Ví dụ: tiêu đề của Visual C ++ 2015 chỉ định đó int_fast16_tlà số nguyên 32 bit vì số học 32 bit nói chung nhanh hơn trên x86 so với số học 16 bit.

Điều này rất quan trọng vì bạn sẽ có thể sử dụng các loại có thể giữ kết quả tính toán mà chương trình của bạn thực hiện bất kể nền tảng. Nếu một chương trình tạo ra kết quả chính xác trên một nền tảng nhưng kết quả không chính xác trên nền tảng khác do sự khác biệt về tràn số nguyên, điều đó thật tệ. Bằng cách sử dụng các loại số nguyên tiêu chuẩn, bạn đảm bảo rằng kết quả trên các nền tảng khác nhau sẽ giống nhau về kích thước của các số nguyên được sử dụng (tất nhiên có thể có sự khác biệt khác giữa các nền tảng ngoài độ rộng số nguyên).

Vì vậy, có, longnên bị cấm từ mã C ++ hiện đại. Vì vậy nên int, shortlong long.


20
Tôi ước tôi có năm tài khoản khác để bình chọn thêm một số tài khoản này.
Steven Burnap

4
+1, tôi đã xử lý một số lỗi bộ nhớ lạ chỉ xảy ra khi kích thước của cấu trúc phụ thuộc vào máy tính bạn đang biên dịch.
Joshua Snider

9
@Wildcard nó là một tiêu đề C cũng là một phần của C ++: xem tiền tố "c" trên đó. Cũng có một số cách để đặt typedefs vào stdkhông gian tên khi #included trong một đơn vị biên dịch C ++, nhưng tài liệu tôi liên kết không đề cập đến nó và Visual Studio dường như không quan tâm đến cách tôi truy cập chúng.

11
Cấm intcó thể ... quá mức? (Tôi sẽ xem xét nó nếu mã cần phải cực kỳ di động trên tất cả các nền tảng tối nghĩa (và không quá tối nghĩa). Cấm mã cho "mã ứng dụng" có thể không phù hợp lắm với các nhà phát triển của chúng tôi.
Martin Ba

5
@Snowman #include <cstdint>cần thiết để đưa các loại trong std::và (đáng tiếc) tùy chọn cho phép cũng đặt chúng trong không gian tên toàn cầu. #include <stdint.h>chính xác là converse. Điều tương tự áp dụng cho bất kỳ cặp tiêu đề C khác. Xem: stackoverflow.com/a/13643019/2757035 Tôi muốn Standard đã yêu cầu mỗi chỉ ảnh hưởng đến không gian tên được yêu cầu tương ứng của nó - hơn là dường như oằn để ước nghèo thành lập bởi một số hiện thực - nhưng oh well, ở đây chúng tôi đang có.
gạch dưới

38

Không, cấm các loại số nguyên dựng sẵn sẽ là vô lý. Họ cũng không nên bị lạm dụng.

Nếu bạn cần một số nguyên có độ rộng chính xácN bit, hãy sử dụng (hoặc nếu bạn cần một phiên bản). Nghĩ về số nguyên 32 bit và số nguyên 64 bit là sai. Nó có thể xảy ra như thế này trên các nền tảng hiện tại của bạn nhưng điều này phụ thuộc vào hành vi được xác định theo triển khai.std::intN_tstd::uintN_tunsignedintlong long

Sử dụng các loại số nguyên có chiều rộng cố định cũng hữu ích cho việc tương tác với các công nghệ khác. Ví dụ: nếu một số phần trong ứng dụng của bạn được viết bằng Java và các phần khác bằng C ++, có thể bạn sẽ muốn khớp các loại số nguyên để bạn có được kết quả nhất quán. (Vẫn cần lưu ý rằng tràn trong Java có ngữ nghĩa được xác định rõ trong khi signedtràn trong C ++ là hành vi không xác định nên tính nhất quán là mục tiêu cao.) Chúng cũng sẽ là vô giá khi trao đổi dữ liệu giữa các máy chủ khác nhau.

Nếu bạn không cần chính xác N bit, nhưng chỉ cần một loại đủ rộng , hãy cân nhắc sử dụng (tối ưu hóa cho không gian) hoặc (tối ưu hóa cho tốc độ). Một lần nữa, cả hai gia đình cũng có đối tác.std::int_leastN_tstd::int_fastN_tunsigned

Vậy, khi nào nên sử dụng các loại dựng sẵn? Chà, vì tiêu chuẩn không chỉ định chính xác chiều rộng của chúng, hãy sử dụng chúng khi bạn không quan tâm đến chiều rộng bit thực tế mà về các đặc điểm khác.

A charlà số nguyên nhỏ nhất có thể đánh địa chỉ bằng phần cứng. Ngôn ngữ thực sự buộc bạn phải sử dụng nó cho bộ nhớ tùy ý. Nó cũng là loại khả thi duy nhất để biểu diễn các chuỗi ký tự (hẹp).

An intthường sẽ là loại nhanh nhất mà máy có thể xử lý. Nó sẽ đủ rộng để nó có thể được tải và lưu trữ bằng một lệnh duy nhất (không phải che dấu hoặc dịch chuyển bit) và đủ hẹp để có thể được vận hành với (các) phần cứng hiệu quả nhất. Do đó, intlà một lựa chọn hoàn hảo để truyền dữ liệu và thực hiện số học khi tràn không phải là một mối quan tâm. Ví dụ, kiểu liệt kê cơ bản mặc định là int. Đừng thay đổi nó thành số nguyên 32 bit chỉ vì bạn có thể. Ngoài ra, nếu bạn có một giá trị chỉ có thể là1, 0 và 1, mộtintlà một lựa chọn hoàn hảo, trừ khi bạn sẽ lưu trữ các mảng lớn trong số đó trong trường hợp bạn có thể muốn sử dụng loại dữ liệu nhỏ gọn hơn với chi phí phải trả giá cao hơn để truy cập các yếu tố riêng lẻ. Bộ nhớ đệm hiệu quả hơn có thể sẽ trả hết cho những thứ này. Nhiều chức năng hệ điều hành cũng được định nghĩa theo int. Sẽ là ngớ ngẩn khi chuyển đổi các đối số và kết quả của họ qua lại. Tất cả điều này có thể làm là giới thiệu lỗi tràn.

longthường sẽ là loại rộng nhất có thể được xử lý với các hướng dẫn máy đơn. Điều này làm cho đặc biệt unsigned longrất hấp dẫn để xử lý dữ liệu thô và tất cả các loại công cụ thao tác bit. Ví dụ, tôi sẽ thấy unsigned longtrong việc thực hiện một vectơ bit. Nếu mã được viết cẩn thận, thì loại thực sự rộng bao nhiêu (vì mã sẽ tự động thích ứng). Trên các nền tảng có từ máy gốc là 32 bit, có mảng sao lưu của vectơ bit là một mảng củaunsignedSố nguyên 32 bit là mong muốn nhất bởi vì thật ngớ ngẩn khi sử dụng loại 64 bit phải được tải thông qua các hướng dẫn đắt tiền chỉ để dịch chuyển và che dấu các bit không cần thiết đi nữa. Mặt khác, nếu kích thước từ gốc của nền tảng là 64 bit, tôi muốn một mảng loại đó bởi vì điều đó có nghĩa là các hoạt động như tìm thấy tập đầu tiên có thể chạy nhanh gấp đôi. Vì vậy, vấn đề của longLỚP về loại dữ liệu mà bạn mô tả, kích thước của nó thay đổi tùy theo nền tảng, thực sự là một tính năng có thể được sử dụng tốt. Nó chỉ trở thành một vấn đề nếu bạn nghĩ về các loại dựng sẵn là các loại có độ rộng bit nhất định, mà chúng đơn giản là không.

char, intlonglà những loại rất hữu ích như được mô tả ở trên. shortlong longgần như không hữu ích vì ngữ nghĩa của chúng ít rõ ràng hơn nhiều.


4
OP đặc biệt gọi ra sự khác biệt về kích thước longgiữa Windows và Unix. Tôi có thể hiểu nhầm, nhưng mô tả của bạn về sự khác biệt về kích thước của longmột "tính năng" thay vì "vấn đề" đối với tôi khi so sánh các mô hình dữ liệu 32 và 64 bit, nhưng không phải là so sánh cụ thể này. Trong trường hợp cụ thể câu hỏi này được hỏi về, đây có thực sự là một tính năng? Hay nó là một tính năng trong các tình huống khác (nghĩa là nói chung) và vô hại trong trường hợp này?
Dan Getz

3
@ 5gon12eder: Vấn đề là các loại như uint32_t được tạo ra với mục đích cho phép hành vi của mã độc lập với kích thước của "int", nhưng thiếu một loại có nghĩa là "hoạt động như một uint32_t hoạt động trên 32- hệ thống bit "làm cho mã viết có hành vi độc lập chính xác với kích thước của" int "khó hơn nhiều so với viết mã gần như đúng.
supercat

3
Vâng, tôi biết ... đó là nơi mà những lời chửi rủa bắt nguồn. Các tác giả ban đầu chỉ đi theo con đường kháng chiến cho thuê vì khi họ viết mã, các hệ điều hành 32 bit đã cách đây hơn một thập kỷ.
Steven Burnap

8
@ 5gon12eder Đáng buồn thay, supercat là chính xác. Tất cả các loại chính xác byte là "chỉ Typedefs" và các quy tắc thúc đẩy số nguyên không để ý trong số họ, có nghĩa là số học trên uint32_tgiá trị này sẽ được thực hiện như đã ký , intsố học -width trên một nền tảng mà intrộng hơn uint32_t. (Với ABI ngày nay, điều này rất có thể là một vấn đề đối với uint16_t.)
zwol

9
1, cảm ơn cho một câu trả lời chi tiết. Nhưng: trời ơi. Đoạn văn dài của bạn: " longthường sẽ là loại rộng nhất có thể được xử lý bằng các lệnh máy đơn lẻ. ..." - và điều này hoàn toàn sai . Nhìn vào mô hình dữ liệu Windows. IMHO, toàn bộ ví dụ sau của bạn bị hỏng, vì trên x64 Windows dài vẫn là 32 bit.
Martin Ba

6

Một câu trả lời khác đã được xây dựng trên các loại cstdint và các biến thể ít được biết đến trong đó.

Tôi muốn thêm vào đó:

sử dụng tên loại dành riêng cho tên miền

Đó là, đừng khai báo các tham số và biến của bạn là uint32_t(chắc chắn là không long!), Nhưng các tên như channel_id_type, room_count_typev.v.

về thư viện

Các thư viện bên thứ 3 sử dụng longhoặc không có gì có thể gây phiền nhiễu, đặc biệt nếu được sử dụng làm tài liệu tham khảo hoặc con trỏ cho những người đó.

Điều tốt nhất là làm cho giấy gói.

Nói chung, chiến lược của tôi là tạo ra một tập hợp các hàm giống như diễn viên sẽ được sử dụng. Chúng bị quá tải để chỉ chấp nhận những loại khớp chính xác với các loại tương ứng, cùng với bất kỳ con trỏ nào, v.v. Chúng được định nghĩa cụ thể cho os / trình biên dịch / cài đặt. Điều này cho phép bạn xóa các cảnh báo và đảm bảo rằng chỉ các chuyển đổi "đúng" được sử dụng.

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

Đặc biệt, với các loại nguyên thủy khác nhau tạo ra 32 bit, sự lựa chọn của bạn về cách int32_txác định có thể không khớp với lệnh gọi thư viện (ví dụ: int vs long trên Windows).

Hàm giống như tài liệu xung đột, cung cấp cho việc kiểm tra thời gian biên dịch trên kết quả khớp với tham số của hàm và xóa bất kỳ cảnh báo hoặc lỗi nào nếu và chỉ khi loại thực tế khớp với kích thước thực có liên quan. Đó là, nó bị quá tải và được xác định nếu tôi truyền vào (trên Windows) một int*hoặc một lỗi long*và đưa ra lỗi thời gian biên dịch theo cách khác.

Vì vậy, nếu thư viện được cập nhật hoặc ai đó thay đổi những gì channel_id_type, điều này tiếp tục được xác minh.


Tại sao downvote (không có bình luận)?
JDługosz

Bởi vì hầu hết các lượt tải xuống trên mạng này xuất hiện mà không có bình luận ...
Ruslan
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.