Làm thế nào hữu ích là kích thước các biến thực sự của C?


9

Một điều luôn luôn đánh vào trực giác tôi là một tính năng tích cực của C (thực ra là các triển khai của nó như gcc, clang, ...) là thực tế là nó không lưu trữ bất kỳ thông tin ẩn nào bên cạnh các biến của bạn khi chạy. Điều này có nghĩa là nếu bạn muốn có một biến "x" thuộc loại "uint16_t", bạn có thể chắc chắn rằng "x" sẽ chỉ chiếm 2 byte không gian (và sẽ không mang bất kỳ thông tin ẩn nào như kiểu của nó, v.v. .). Tương tự, nếu bạn muốn một mảng gồm 100 số nguyên, bạn có thể chắc chắn rằng nó lớn bằng 100 số nguyên.

Tuy nhiên, tôi càng cố gắng đưa ra các trường hợp sử dụng cụ thể cho tính năng này, tôi càng tự hỏi liệu nó có thực sự có bất kỳ lợi thế thực tế nào không . Điều duy nhất tôi có thể nghĩ ra cho đến nay là nó rõ ràng cần ít RAM hơn. Đối với các môi trường hạn chế, như chip AVR, v.v., đây chắc chắn là một điểm cộng rất lớn, nhưng đối với các trường hợp sử dụng máy tính để bàn / máy chủ hàng ngày, nó dường như không liên quan. Một khả năng khác mà tôi nghĩ đến là nó thể hữu ích / quan trọng cho việc truy cập phần cứng hoặc có thể ánh xạ các vùng bộ nhớ (ví dụ như đầu ra VGA và tương tự) ...?

Câu hỏi của tôi: Có bất kỳ lĩnh vực cụ thể nào có thể hoặc chỉ có thể được thực hiện một cách rườm rà mà không có tính năng này không?

PS Xin vui lòng cho tôi biết nếu bạn có một tên tốt hơn cho nó! ;)



@gnat Tôi nghĩ tôi hiểu vấn đề của bạn là gì. Đó là bởi vì có thể có nhiều câu trả lời, phải không? Chà, tôi hiểu rằng câu hỏi này có thể không phù hợp với cách hoạt động của stackexchange, nhưng thật lòng tôi không biết phải hỏi ở đâu khác ...
Thomas Oltmann 16/1/2016

1
@lxrec RTTI được lưu trữ trong vtable và các đối tượng chỉ lưu trữ một con trỏ tới vtable. Ngoài ra, các loại chỉ có RTTI nếu chúng đã có vtable vì chúng có virtualchức năng thành viên. Vì vậy, RTTI không bao giờ tăng kích thước của bất kỳ đối tượng nào, nó chỉ làm cho nhị phân lớn hơn một hằng số.

3
@ThomasOltmann Mọi đối tượng có phương thức ảo đều cần một con trỏ vtable. Bạn không thể có các phương thức ảo mà không có. Hơn nữa, bạn rõ ràng chọn tham gia vào các phương thức ảo (và do đó, một vtable).

1
@ThomasOltmann Bạn có vẻ rất bối rối. Nó không phải là một con trỏ tới một đối tượng mang con trỏ vtable, nó là chính đối tượng đó. Tức T *là , luôn có cùng kích thước và Tcó thể chứa một trường ẩn trỏ đến vtable. Và không có trình biên dịch C ++ nào đã chèn vtables vào các đối tượng không cần chúng.

Câu trả lời:


5

Có một số lợi ích, một lợi ích rõ ràng là vào thời gian biên dịch để đảm bảo rằng những thứ như tham số hàm khớp với các giá trị được truyền vào.

Nhưng tôi nghĩ rằng bạn đang hỏi về những gì đang xảy ra trong thời gian chạy.

Hãy nhớ rằng trình biên dịch sẽ tạo ra một thời gian chạy gắn kiến ​​thức về các loại dữ liệu trong các hoạt động mà nó thực hiện. Mỗi khối dữ liệu trong bộ nhớ có thể không tự mô tả, nhưng mã vốn đã biết dữ liệu đó là gì (nếu bạn đã thực hiện đúng công việc của mình).

Trong thời gian chạy mọi thứ có một chút khác biệt so với bạn nghĩ.

Ví dụ: đừng cho rằng chỉ có hai byte được sử dụng khi bạn khai báo uint16_t. Tùy thuộc vào bộ xử lý và căn chỉnh từ, nó có thể chiếm 16, 32 hoặc 64 bit trên ngăn xếp. Bạn có thể thấy rằng mảng quần short của bạn tiêu thụ nhiều bộ nhớ hơn bạn mong đợi.

Điều này có thể có vấn đề trong một số tình huống mà bạn cần tham chiếu dữ liệu tại các độ lệch cụ thể. Điều này xảy ra khi giao tiếp giữa hai hệ thống có kiến ​​trúc bộ xử lý khác nhau, thông qua liên kết không dây hoặc qua tệp.

C cho phép bạn chỉ định các cấu trúc với độ chi tiết của bit:

struct myMessage {
  uint8_t   first_bit: 1;
  uint8_t   second_bit: 1;
  uint8_t   padding:6;
  uint16_t  somethingUseful;
}

Cấu trúc này dài ba byte, với một khoảng ngắn được xác định để bắt đầu ở phần bù lẻ. Nó cũng sẽ cần phải được đóng gói để chính xác như bạn đã xác định. Nếu không, trình biên dịch sẽ sắp xếp các thành viên.

Trình biên dịch sẽ tạo mã phía sau hậu trường để trích xuất dữ liệu này và sao chép vào một thanh ghi để bạn có thể làm những việc hữu ích với nó.

Bây giờ bạn có thể thấy rằng mỗi khi chương trình của tôi truy cập vào một thành viên của cấu trúc myMessage, nó sẽ biết chính xác làm thế nào để giải nén nó và vận hành trên nó.

Điều này có thể trở nên rắc rối và khó quản lý khi giao tiếp giữa các hệ thống khác nhau với các phiên bản phần mềm khác nhau. Bạn phải cẩn thận thiết kế hệ thống & mã để đảm bảo cả hai bên có cùng định nghĩa chính xác về các loại dữ liệu. Điều này có thể khá khó khăn trong một số môi trường. Đây là nơi bạn cần một giao thức tốt hơn chứa dữ liệu tự mô tả, chẳng hạn như Bộ đệm giao thức của Google .

Cuối cùng, bạn đưa ra một điểm tốt để hỏi tầm quan trọng của điều này trong môi trường máy tính để bàn / máy chủ. Nó thực sự phụ thuộc vào dung lượng bộ nhớ bạn định sử dụng. Nếu bạn đang làm một cái gì đó như xử lý hình ảnh, cuối cùng bạn có thể sử dụng một lượng lớn bộ nhớ có thể ảnh hưởng đến hiệu suất của ứng dụng của bạn. Điều này chắc chắn luôn là mối quan tâm trong môi trường nhúng nơi bộ nhớ bị hạn chế và không có bộ nhớ ảo.


2
"Bạn có thể thấy rằng mảng quần short của bạn tiêu thụ nhiều bộ nhớ hơn bạn mong đợi." Điều này là sai trong C: Mảng được đảm bảo để chứa các yếu tố của chúng theo cách không có khoảng cách. Có, mảng cần được căn chỉnh chính xác, cũng như một mảng short. Nhưng đây là yêu cầu một lần cho việc bắt đầu mảng, phần còn lại được tự động căn chỉnh chính xác nhờ tính liên tiếp.
cmaster - phục hồi monica

Ngoài ra, cú pháp cho phần đệm là sai, nó sẽ uint8_t padding: 6;giống như hai bit đầu tiên. Hoặc, rõ ràng hơn, chỉ là nhận xét //6 bits of padding inserted by the compiler. Cấu trúc, như bạn đã viết, có kích thước ít nhất là chín byte, không phải ba byte.
cmaster - phục hồi monica

9

Bạn nhấn vào một trong những lý do duy nhất này hữu ích: ánh xạ cấu trúc dữ liệu ngoài. Chúng bao gồm bộ đệm video được ánh xạ bộ nhớ, thanh ghi phần cứng, v.v. Chúng cũng bao gồm dữ liệu được truyền nguyên vẹn bên ngoài chương trình, như chứng chỉ SSL, gói IP, hình ảnh JPEG và hầu hết mọi cấu trúc dữ liệu khác có tuổi thọ bền vững bên ngoài chương trình.


5

C là một ngôn ngữ cấp thấp, gần như một trình biên dịch di động, do đó cấu trúc dữ liệu và cấu trúc ngôn ngữ của nó gần với kim loại (cấu trúc dữ liệu không có chi phí ẩn - ngoại trừ các giới hạn đệm, căn chỉnh và kích thước được áp đặt bởi phần cứng và ABI ). Vì vậy, C thực sự không có gõ động tự nhiên. Nhưng nếu bạn cần nó, bạn có thể chấp nhận một quy ước rằng tất cả các giá trị của bạn là tổng hợp bắt đầu bằng một số thông tin loại (ví dụ: một số enum...); sử dụng union-s và (đối với những thứ giống như mảng) thành viên mảng linh hoạt trong việc structchứa cả kích thước của mảng.

(khi lập trình trong C, đó là trách nhiệm của bạn để xác định, tài liệu, và làm theo ước hữu ích - điều kiện và bất biến đáng chú ý trước và hậu; cũng cấp phát bộ nhớ động C đòi hỏi expliciting ước về việc ai nên freemột số heap- mallockhu ated bộ nhớ)

Vì vậy, để đại diện cho các giá trị mà là các số nguyên đóng hộp, hoặc các chuỗi, hoặc một số loại Scheme -like biểu tượng , hoặc vectơ các giá trị, bạn sẽ khái niệm sử dụng một công đoàn được gắn thẻ (thực hiện như một sự kết hợp của con trỏ) -always bắt đầu bằng việc loại loại -, ví dụ:

enum value_kind_en {V_NONE, V_INT, V_STRING, V_SYMBOL, V_VECTOR};
union value_en { // this union takes a word in memory
   const void* vptr; // generic pointer, e.g. to free it
   enum value_kind_en* vkind; // the value of *vkind decides which member to use
   struct intvalue_st* vint;
   struct strvalue_st* vstr;
   struct symbvalue_st* vsymb;
   struct vectvalue_st* vvect;
};
typedef union value_en value_t;
#define NULL_VALUE  ((value_t){NULL})
struct intvalue_st {
  enum value_kind_en kind; // always V_INT for intvalue_st
  int num;
};
struct strvalue_st {
  enum value_kind_en kind; // always V_STRING for strvalue_st
  const char*str;
};
struct symbvalue_st {
  enum value_kind_en kind; // V_SYMBOL
  struct strvalue_st* symbname;
  value_t symbvalue;
};
struct vectvalue_st {
  enum value_kind_en kind; // V_VECTOR;
  unsigned veclength;
  value_t veccomp[]; // flexible array of veclength components.
};

Để có được kiểu động của một số giá trị

enum value_kind_en value_type(value_t v) {
  if (v.vptr != NULL) return *(v.vkind);
  else return V_NONE;
}

Đây là một "diễn viên động" cho vectơ:

struct vectvalue_st* dyncast_vector (value_t v) {
   if (value_type(v) == V_VECTOR) return v->vvect;
   else return NULL;
}

và một "bộ truy cập an toàn" bên trong các vectơ:

value_t vector_nth(value_t v, unsigned rk) {
   struct vectvalue_st* vecp = dyncast_vector(v);
   if (vecp && rk < vecp->veclength) return vecp->veccomp[rk];
   else return NULL_VALUE;
}

Thông thường bạn sẽ xác định hầu hết các chức năng ngắn ở trên như static inlinetrong một số tệp tiêu đề.

BTW, nếu bạn có thể sử dụng trình thu gom rác của Boehm, thì bạn có thể mã hóa khá dễ dàng theo một số kiểu cấp cao hơn (nhưng không an toàn) và một số trình thông dịch Scheme được thực hiện theo cách đó. Một constructor vector matrixdic có thể là

value_t make_vector(unsigned size, ... /*value_t arguments*/) {
   struct vectvalue_st* vec = GC_MALLOC(sizeof(*vec)+size*sizeof(value));
   vec->kind = V_VECTOR;
   va_args args;
   va_start (args, size);
   for (unsigned ix=0; ix<size; ix++) 
     vec->veccomp[ix] = va_arg(args,value_t);
   va_end (args);
   return (value_t){vec};
}

và nếu bạn có ba biến

value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;

bạn có thể xây dựng một vectơ từ chúng bằng cách sử dụng make_vector(3,v1,v2,v3)

Nếu bạn không muốn sử dụng công cụ thu gom rác của Boehm (hoặc thiết kế công cụ dọn rác của riêng bạn), bạn nên hết sức cẩn thận trong việc xác định các hàm hủy và ghi lại ai, làm thế nào và khi nào bộ nhớ phải là free-d; xem ví dụ này Vì vậy, bạn có thể sử dụng malloc(nhưng sau đó kiểm tra chống lại sự thất bại của nó) thay vì GC_MALLOCở trên nhưng bạn cần xác định cẩn thận và sử dụng một số hàm hủyvoid destroy_value(value_t)

Điểm mạnh của C là ở mức độ đủ thấp để tạo mã như trên có thể và xác định các quy ước của riêng bạn (cụ thể là phần mềm của bạn).


Tôi nghĩ rằng bạn đã hiểu nhầm câu hỏi của tôi. Tôi không muốn gõ động trong C. Tôi tò mò liệu thuộc tính cụ thể này của C có sử dụng thực tế không.
Thomas Oltmann

Nhưng tài sản chính xác của C mà bạn đang đề cập đến là gì? Cấu trúc dữ liệu C gần với kim loại, do đó không có chi phí ẩn (ngoại trừ các ràng buộc về kích thước và kích thước)
Basile Starynkevitch

Chính xác là: /
Thomas Oltmann 17/1/2016

C được phát minh như một ngôn ngữ cấp thấp, nhưng khi tối ưu hóa được bật các trình biên dịch như gcc xử lý một ngôn ngữ sử dụng cú pháp cấp thấp nhưng không cung cấp quyền truy cập ở mức độ thấp để đảm bảo hành vi được cung cấp nền tảng. Người ta cần sizeof để sử dụng malloc và memcpy, nhưng sử dụng để tính toán địa chỉ fancier có thể không được hỗ trợ trong "hiện đại" C.
supercat
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.