2012-04-19 35 views
11

Tôi đang phát triển một số dự án C và C++ cần phải di chuyển trên nhiều nền tảng máy tính để bàn và thiết bị di động. Tôi biết điều quan trọng là sử dụng các loại có kích thước rõ ràng u32_t i64_ t vv khi tôi đọc và ghi dữ liệu vào đĩa.những hạn chế hoặc sự cân bằng của việc sử dụng các loại có kích thước rõ ràng trong ngôn ngữ gia đình C

Bạn nên sử dụng loại có kích thước rõ ràng của tất cả các loại số nguyên để đảm bảo thực thi nhất quán? Tôi đã nghe nói rằng một chiến lược tốt là sử dụng các loại có kích thước rõ ràng trong nội bộ cho các thành viên dữ liệu lớp nhưng không phải trong các giao diện.

Có bất kỳ phương pháp hay nhất nào về các loại có kích thước rõ ràng trên các thành viên và giao diện dữ liệu không? (Tôi giả định rằng sẽ không có sự khác biệt lớn giữa C hoặc C++ trong các trường hợp này, nhưng hãy cho tôi biết nếu có)

+6

Miễn là chúng di động bằng hoặc nhiều hơn các lựa chọn thay thế, tôi sẽ tưởng tượng có * không phải là một mặt khác ngoài việc đảm bảo bạn thiết kế nó rất tốt ngay từ đầu để sau đó bạn không không phải thay đổi loại hoặc mã của bạn. Nhược điểm duy nhất có thể là một số nguyên 32 bit (i32b hoặc bất cứ điều gì) có thể chiếm gấp đôi không gian như là một 'int' là cần thiết quá. Nhưng, tôi muốn biết các loại dữ liệu của mình lớn đến mức nào, vì vậy tôi không thấy đó là một nhược điểm. Nếu tôi muốn> = 16 bit, tôi sẽ sử dụng> = 16 bit, chứ không phải 32. Ngoài ra, bạn đã thấy stdint.h chưa? Có vẻ như bạn đang sáng tạo lại. – Corbin

+4

Không có ? – Mehrdad

+0

Ah, thực sự tôi đã bỏ lỡ một nhược điểm thực sự rõ ràng. Không tương thích với mã của người khác. Tôi sẽ tưởng tượng rằng int32b của bạn hoặc bất cứ điều gì gần như luôn luôn sẽ tương thích hoàn toàn với một 'int', nhưng có thể có trường hợp nó sẽ không được (và do đó không nên được giả định). – Corbin

Trả lời

10

Điều thú vị về loại "int" cơ bản là nó sẽ luôn luôn là loại số nguyên nhanh nhất cho bất kỳ nền tảng nào bạn hiện đang biên dịch. Mặt khác, lợi thế của việc sử dụng, nói, int32_t (thay vì chỉ int) là mã của bạn có thể đếm trên một int32_t luôn luôn là 32 bit rộng không có vấn đề gì nền tảng nó được biên dịch trên, có nghĩa là bạn có thể một cách an toàn đưa ra nhiều giả định về hành vi của giá trị hơn là bạn có thể với một int. Với các loại kích thước cố định, nếu mã của bạn biên dịch hoàn toàn trên nền tảng Y mới, thì có nhiều khả năng ứng xử giống hệt như trên nền tảng cũ X.

(lý thuyết) bất lợi của int32_t là nền tảng X mới có thể không hỗ trợ các số nguyên 32 bit (trong trường hợp đó mã của bạn sẽ không biên dịch ở tất cả trên nền tảng đó), hoặc nó có thể hỗ trợ chúng nhưng xử lý chúng chậm hơn nó sẽ xử lý các int cũ.

Các ví dụ trên có một chút khó khăn, vì hầu như tất cả phần cứng hiện đại xử lý các số nguyên 32 bit ở tốc độ tối đa, nhưng có (và làm) tồn tại nền tảng nơi thao tác int64_ts chậm hơn thao tác int, vì (a) CPU có các thanh ghi 32 bit, và do đó phải chia từng hoạt động thành nhiều bước, và dĩ nhiên (b) một số nguyên 64 bit sẽ chiếm gấp đôi bộ nhớ làm số nguyên 32 bit, có thể gây thêm áp lực lên bộ đệm . Tuy nhiên, hãy nhớ rằng 99% phần mềm mọi người viết, vấn đề này sẽ không có bất kỳ hiệu ứng quan sát nào về hiệu suất, đơn giản vì 99% phần mềm không có CPU bị ràng buộc trong những ngày này, và ngay cả đối với mã có nghĩa là, nó không chắc rằng chiều rộng số nguyên sẽ là vấn đề hiệu suất lớn. Vì vậy, những gì nó thực sự đi xuống là, làm thế nào để bạn muốn toán số nguyên của bạn để hành xử?

  • Nếu bạn muốn trình biên dịch để đảm bảo rằng giá trị số nguyên của bạn luôn chiếm 32 bit RAM, và sẽ luôn luôn "quấn quanh" tại 2^31 (hoặc 2^32 cho unsigned), không có vấn đề bạn đang biên dịch nền tảng nào, đi với int32_t (v.v.).

  • Nếu bạn không thực sự quan tâm đến hành vi gói (vì bạn biết các số nguyên của bạn sẽ không bao giờ được bọc, do tính chất của dữ liệu mà chúng đang lưu trữ) và bạn muốn làm cho mã di động hơn một chút mục tiêu biên dịch kỳ lạ/bất thường, và ít nhất về mặt lý thuyết nhanh hơn (mặc dù có lẽ không phải trong cuộc sống thực), thì bạn có thể gắn bó với đồng bằng cũ ngắn/int/dài.

Cá nhân tôi sử dụng các loại kích thước cố định (int32_t, vv) theo mặc định, trừ khi có một lý do rất rõ ràng không, bởi vì tôi muốn giảm thiểu số lượng hành vi biến trên nhiều nền tảng. Ví dụ, đoạn mã này:

for (uint32_t i=0; i<4000000000; i++) foo(); 

... sẽ luôn luôn gọi foo() chính xác 4000000000 lần, trong khi mã này:

for (unsigned int i=0; i<4000000000; i++) foo(); 

sức gọi foo() 4000000000 lần, hoặc nó có thể đi vào một vòng lặp vô hạn, tùy thuộc vào việc (sizeof (int)> = 4) hay không. Chắc chắn sẽ có thể xác minh rằng đoạn mã thứ hai không làm điều đó trên bất kỳ nền tảng nhất định nào, nhưng do sự khác biệt hiệu suất có khả năng giữa hai kiểu, tôi thích cách tiếp cận đầu tiên kể từ khi dự đoán hành vi của nó là không có trí tuệ. Tôi nghĩ rằng cách tiếp cận char/short/int/long hữu ích hơn trong những ngày đầu của C, khi kiến ​​trúc máy tính đa dạng hơn, và CPU đủ chậm để đạt được hiệu năng gốc hoàn toàn quan trọng hơn mã hóa an toàn.

+0

Cảm ơn, đó là một câu trả lời hoàn hảo! –

+5

lưu ý rằng tràn trên ints đã ký là không xác định, vì vậy nếu bạn muốn gói hành vi, luôn luôn sử dụng các loại unsigned. –

+0

[Cách tiếp cận các loại] của Rust (https://doc.rust-lang.org/book/primitive-types.html) là cho 'u8',' i32', 'u64', v.v ... là các kiểu nguyên thủy. Có 'isize' /' usize' cho số nguyên có kích thước con trỏ, nhưng nếu không chỉ có kích thước cố định. Vì vậy, các nhà thiết kế ngôn ngữ cảm thấy rằng nó ** không quan trọng đối với phần cứng hiện đại để cung cấp các số nguyên phụ thuộc vào triển khai **, vì 32bit nhanh trên mọi thứ quan trọng và đủ lớn để sử dụng nhiều nhất. (Hãy thử một cách mệt mỏi nếu bạn mệt mỏi vì sự từ chối của C để tiêu chuẩn hóa bổ sung/gói bổ sung của hai, hoặc những thứ như 'popcnt', thay đổi số học, quay, vv ..) –

6

Sử dụng inttypes.h hoặc stdint.h. Nó là ANSI-C, do đó, nó sẽ được hỗ trợ bởi bất kỳ chuỗi công cụ nào nhằm mục đích tuân thủ ANSI.

Hơn nữa, nó vẫn giúp bạn tiết kiệm công sức để tái phát minh bánh xe.

Điều duy nhất bạn phải làm là

#include <inttypes.h> 

uint32_t integer_32bits_nosign; 
  • hơn Một lo ngại về tính di động: Vì vậy, quan trọng là chiều rộng dữ liệu là dữ liệu endianess. Bạn phải kiểm tra endianess mục tiêu với các macro tiêu chuẩn:

    struct { 
    #if defined(__BIG_ENDIAN__) || defined(_BIG_ENDIAN) 
        // Data disposition for Big Endian 
    #else 
        // Data disposition for Little Endian 
    #endif 
    }; 
    

Nó là đặc biệt sensitiive nếu bạn sử dụng bit lĩnh vực.


EDIT:

Tất nhiên bạn có thể sử dụng <csdtint> như những người khác đề nghị nếu bạn có kế hoạch để sử dụng nó trên C++ chỉ mã.

+0

Ok, do đó, bạn có nghĩ rằng nó là một ý tưởng tốt để sử dụng chúng trên cho các thành viên dữ liệu vv? –

+0

Giao diện lớp học của bạn là những hợp đồng quan trọng nhất, vì vậy hãy tập trung vào chúng. Và tôi sẽ viết giao diện với ints. Như để lưu trữ chúng, bạn có phải không? Nếu không thì dùng int. Nếu bạn ** phải ** thì hãy chỉ định kích thước. Để lưu trữ dữ liệu trong các tệp, hãy thực hiện điều này như có thể dự đoán được, do đó, có giao diện không làm gì ngoài mã hóa và giải mã sao cho mã của bạn không phải quan tâm đến endianness –

+0

@ JSPerfUnkn0wn tại sao bạn lại muốn làm cho giao diện của tôi sử dụng loại chung? –

0

Có loại fast_ của số nguyên kích thước trong stdint.h, trình biên dịch sẽ chọn số nguyên nhanh nhất với kích thước yêu cầu trong nền tảng này, ví dụ (chiết xuất từ ​​stdint.h)

typedef signed char  int_fast8_t; 
#if __WORDSIZE == 64 
typedef long int  int_fast16_t; 
typedef long int  int_fast32_t; 
typedef long int  int_fast64_t; 
#else 
typedef int   int_fast16_t; 
typedef int   int_fast32_t; 
__extension__ 
typedef long long int  int_fast64_t; 
#endif 
+0

Câu hỏi đặt ra là, nhanh cho cái gì? Đối với các thời gian, 'int32_t' hơi nhanh hơn' int64_t' trên x86-64. (Kích thước mã nhỏ hơn (tiền tố REX thường không cần thiết) và nhân nhanh hơn trên pre-silvermont Atom). Và nếu bạn lưu trữ chúng vào bộ nhớ, phải mất 8B thay vì 4B. Tôi nghĩ rằng 64b 'int_fast32_t' là một lựa chọn thực sự kỳ lạ trên x86-64, nhưng đó là sự lựa chọn của họ. –

1

Một khá khó chịu " gotcha "với các loại có kích thước cố định là trong khi chúng cho ấn tượng rằng mã không phụ thuộc vào kích thước của" int ", đó thực sự là một ảo tưởng.Một đoạn mã như:

uint32_t mul(uint16_t a, uint16_t b) 
{ return a*b; } 

sẽ có một ý nghĩa được xác định cho tất cả các giá trị của "a" và "b" trên tất cả các nền tảng nơi "int" là 40 bit hoặc lớn hơn, và cũng có thể sẽ được xác định ý nghĩa cho tất cả giá trị của "a" và "b" trên tất cả các nền tảng có "int" là 16 bit, mặc dù ý nghĩa sẽ khác khi sản phẩm số học là 65535. Các tác giả của tiêu chuẩn C89 lưu ý rằng mặc dù không bắt buộc phải làm như vậy , phần lớn các triển khai của thời đại đó đã xác định hành vi toán học số nguyên của chúng sao cho hầu hết các hoạt động đã ký - với một vài ngoại lệ cụ thể - sẽ hoạt động giống hệt nhau với eir unsigned counterparts ngay cả khi kết quả nằm giữa INT_MAX + 1 và UINT_MAX - và do đó trên những trình biên dịch đó, tất cả các giá trị của "a" và "b" sẽ khớp với hành vi trên các máy có loại "int" lớn hơn . Tuy nhiên, có trở nên thời trang đối với các trình biên dịch 32 bit để tạo mã sẽ ngắt với các giá trị lớn hơn INT_MAX vì tiêu chuẩn không cấm chúng làm như vậy.

+0

Cảm ơn bạn! Đây là thông tin rất hữu ích. –

+0

@JustinMeiners: Tôi tự hỏi làm thế nào tôi nên thể hiện tốt nhất thực tế là mã dựa vào hành vi hợp lý cho các giá trị INT_MAX + 1 tới UINT_MAX sẽ * gần như luôn hoạt động, ngay cả với các trình biên dịch hiện đại, nhưng những thay đổi nhỏ đối với mã có thể cho phép trình biên dịch thấy một "tối ưu hóa" mà trước đây nó đã bỏ qua, vì vậy người ta không thể dựa vào bất kỳ mã nào làm việc trừ khi một người vô hiệu hóa một loạt các tối ưu hóa hữu ích ngoài việc tối ưu hóa "tối ưu" siêu hiện đại. – supercat

Các vấn đề liên quan