Tại sao mảng C không theo dõi chiều dài của chúng?


77

Lý do đằng sau không lưu trữ rõ ràng độ dài của một mảng với một mảng là Cgì?

Theo cách tôi thấy, có rất nhiều lý do để làm như vậy nhưng không có nhiều sự hỗ trợ của tiêu chuẩn (C89). Ví dụ:

  1. Có chiều dài có sẵn trong một bộ đệm có thể ngăn chặn tràn bộ đệm.
  2. Kiểu Java arr.lengthvừa rõ ràng vừa giúp người lập trình không phải duy trì nhiều ints trên ngăn xếp nếu xử lý một số mảng
  3. Các tham số chức năng trở nên hợp tác hơn.

Nhưng có lẽ lý do thúc đẩy nhất, theo tôi, là thông thường, không có không gian nào được lưu mà không giữ được chiều dài. Tôi muốn mạo hiểm nói rằng hầu hết việc sử dụng mảng liên quan đến phân bổ động. Đúng, có thể có một số trường hợp mọi người sử dụng một mảng được phân bổ trên ngăn xếp, nhưng đó chỉ là một hàm gọi * - ngăn xếp có thể xử lý thêm 4 hoặc 8 byte.

Vì trình quản lý heap phải theo dõi kích thước khối miễn phí được sử dụng bởi mảng được phân bổ động, tại sao không làm cho thông tin đó có thể sử dụng được (và thêm quy tắc bổ sung, được kiểm tra tại thời điểm biên dịch, rằng người ta không thể thao tác độ dài một cách rõ ràng trừ khi người ta sẽ thích tự bắn vào chân mình).

Điều duy nhất tôi có thể nghĩ đến ở phía bên kia là không theo dõi chiều dài có thể đã thực hiện các trình biên dịch đơn giản, nhưng không phải đơn giản hơn nhiều.

* Về mặt kỹ thuật, người ta có thể viết một số loại hàm đệ quy với một mảng có lưu trữ tự động, và trong trường hợp (rất công phu) này lưu trữ độ dài thực sự có thể dẫn đến việc sử dụng không gian hiệu quả hơn.


6
Tôi cho rằng có thể lập luận rằng, khi C bao gồm sử dụng các cấu trúc làm các loại giá trị tham số và trả về, thì nó nên bao gồm đường cú pháp cho "vectơ" (hoặc bất kỳ tên nào), bên dưới có cấu trúc có độ dài và mảng hoặc con trỏ tới mảng . Hỗ trợ mức độ ngôn ngữ cho cấu trúc chung này (cũng như khi được truyền dưới dạng các đối số riêng biệt và không phải là cấu trúc đơn lẻ) cũng sẽ lưu được vô số lỗi và thư viện chuẩn đơn giản hóa.
hyde

3
Bạn cũng có thể thấy Tại sao Pascal không phải là ngôn ngữ lập trình yêu thích của tôi Phần 2.1 là sâu sắc.

34
Trong khi tất cả các câu trả lời khác có một số điểm thú vị, tôi nghĩ điểm mấu chốt là C được viết để các lập trình viên ngôn ngữ lắp ráp có thể viết mã dễ dàng hơn và có thể mang theo được. Với ý nghĩ đó, việc có một chiều dài mảng được lưu trữ VỚI một mảng tự động sẽ gây phiền toái và không phải là một thiếu sót (như sẽ có những mong muốn phủ kẹo đẹp khác). Các tính năng này có vẻ tốt hiện nay, nhưng trước đó nó thực sự là một cuộc đấu tranh để ép thêm một byte của chương trình hoặc dữ liệu vào hệ thống của bạn. Việc sử dụng bộ nhớ một cách lãng phí sẽ hạn chế nghiêm trọng việc áp dụng C.
Dunk

6
Phần thực sự của câu trả lời của bạn đã được trả lời nhiều lần theo cách tôi sẽ có, nhưng tôi có thể rút ra một điểm khác: "Tại sao kích thước của một malloc()khu vực ed không thể được yêu cầu theo cách di động?" Đó là một điều khiến tôi tự hỏi nhiều lần.
glglgl

5
Bỏ phiếu để mở lại. Có một số lý do ở đâu đó, ngay cả khi chỉ đơn giản là "K & R không nghĩ về nó".
Telastyn

Câu trả lời:


106

Mảng C theo dõi chiều dài của chúng, vì chiều dài mảng là thuộc tính tĩnh:

int xs[42];  /* a 42-element array */

Bạn thường không thể truy vấn độ dài này, nhưng bạn không cần phải vì dù sao nó cũng tĩnh - chỉ cần khai báo một macro XS_LENGTHcho độ dài và bạn đã hoàn thành.

Vấn đề quan trọng hơn là các mảng C hoàn toàn suy biến thành các con trỏ, ví dụ như khi được truyền cho một hàm. Điều này có ý nghĩa gì đó, và cho phép một số thủ thuật cấp thấp đẹp, nhưng nó làm mất thông tin về độ dài của mảng. Vì vậy, một câu hỏi tốt hơn sẽ là tại sao C được thiết kế với sự xuống cấp ngầm định này cho con trỏ.

Một vấn đề khác là con trỏ không cần lưu trữ ngoại trừ chính địa chỉ bộ nhớ. C cho phép chúng ta truyền các số nguyên cho con trỏ, con trỏ tới các con trỏ khác và xử lý các con trỏ như thể chúng là các mảng. Trong khi thực hiện điều này, C không đủ điên rồ để chế tạo một số chiều dài mảng tồn tại, nhưng dường như tin tưởng vào phương châm của Người nhện: với sức mạnh lớn, lập trình viên sẽ hy vọng hoàn thành trách nhiệm lớn trong việc theo dõi độ dài và tràn.


13
Tôi nghĩ bạn muốn nói, nếu tôi không nhầm, trình biên dịch C theo dõi độ dài mảng tĩnh. Nhưng điều này không tốt cho các hàm chỉ lấy một con trỏ.
VF1

25
@ VF1 có. Nhưng quan trọng Cái này là mảng và con trỏ những thứ khác nhau trong C . Giả sử bạn không sử dụng bất kỳ tiện ích mở rộng trình biên dịch nào, bạn thường không thể tự truyền một mảng cho một hàm, nhưng bạn có thể truyền một con trỏ và lập chỉ mục một con trỏ như thể đó là một mảng. Bạn đang phàn nàn một cách hiệu quả rằng con trỏ không có chiều dài kèm theo. Bạn nên phàn nàn rằng các mảng không thể được chuyển qua dưới dạng đối số hàm hoặc mảng đó suy giảm thành con trỏ hoàn toàn.
amon

37
"Bạn thường không thể truy vấn độ dài này" - thực tế bạn có thể, đó là toán tử sizeof - sizeof (xs) sẽ trả về 168 giả sử int dài bốn byte. Để có được 42, hãy làm: sizeof (xs) / sizeof (int)
tcrosley

15
@tcrosley Điều đó chỉ hoạt động trong phạm vi khai báo mảng - hãy thử chuyển xs làm tham số cho hàm khác rồi xem sizeof (xs) mang lại cho bạn ...
Gwyn Evans

26
@GwynEvans một lần nữa: con trỏ không phải là mảng. Vì vậy, nếu bạn chuyển một mảng dưới dạng param sang một hàm khác, bạn sẽ không truyền một mảng mà là một con trỏ. Khẳng định rằng sizeof(xs)vị trí của xsmột mảng sẽ là một cái gì đó khác trong phạm vi khác là hoàn toàn sai, bởi vì thiết kế của C không cho phép các mảng rời khỏi phạm vi của chúng. Nếu sizeof(xs)vị trí của xsmột mảng khác với sizeof(xs)nơi xslà một con trỏ, điều đó không gây ngạc nhiên vì bạn đang so sánh táo với cam .
amon

38

Rất nhiều thứ phải làm với các máy tính có sẵn tại thời điểm đó. Chương trình biên dịch không chỉ phải chạy trên một máy tính tài nguyên hạn chế, mà, có lẽ quan trọng hơn, chính trình biên dịch cũng phải chạy trên các máy này. Vào thời điểm Thompson phát triển C, anh ta đang sử dụng PDP-7, với 8k RAM. Các tính năng ngôn ngữ phức tạp không có sự tương tự ngay lập tức trên mã máy thực tế đơn giản là không được bao gồm trong ngôn ngữ.

Đọc kỹ lịch sử của C mang lại nhiều hiểu biết hơn ở trên, nhưng nó hoàn toàn không phải là kết quả của những hạn chế máy móc mà họ có:

Hơn nữa, ngôn ngữ (C) cho thấy sức mạnh đáng kể để mô tả các khái niệm quan trọng, ví dụ, các vectơ có chiều dài thay đổi theo thời gian chạy, chỉ có một vài quy tắc và quy ước cơ bản. ... Thật thú vị khi so sánh cách tiếp cận của C với hai ngôn ngữ gần như cùng thời, Algol 68 và Pascal [Jensen 74]. Các mảng trong Algol 68 hoặc có giới hạn cố định hoặc 'linh hoạt:' cần có cơ chế đáng kể cả về định nghĩa ngôn ngữ và trong trình biên dịch, để chứa các mảng linh hoạt (và không phải tất cả các trình biên dịch đều thực hiện đầy đủ chúng.) Pascal gốc chỉ có kích thước cố định mảng và chuỗi, và điều này đã chứng minh giới hạn [Kernighan 81].

Mảng C vốn đã mạnh hơn. Thêm giới hạn cho họ hạn chế những gì lập trình viên có thể sử dụng chúng cho. Những hạn chế như vậy có thể hữu ích cho các lập trình viên, nhưng nhất thiết cũng hạn chế.


4
Điều này khá nhiều móng tay câu hỏi ban đầu. Điều đó và thực tế là C đã bị giữ một cách cố ý "chạm nhẹ" khi kiểm tra xem lập trình viên đang làm gì, như là một phần của việc tạo ra sự hấp dẫn cho việc viết các hệ điều hành.
ClickRick

5
Liên kết tuyệt vời, họ cũng thay đổi rõ ràng việc lưu trữ độ dài của chuỗi để sử dụng một dấu phân cách to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator- rất nhiều cho điều đó :-)
Voo

5
Các mảng không hoàn chỉnh cũng phù hợp với cách tiếp cận kim loại trần của C. Hãy nhớ rằng sách K & R C có ít hơn 300 trang với hướng dẫn ngôn ngữ, tài liệu tham khảo và danh sách các cuộc gọi tiêu chuẩn. Cuốn sách O'Reilly Regex của tôi dài gần gấp đôi so với K & R C.
Michael Storesin

22

Quay lại ngày mà C được tạo và thêm 4 byte không gian cho mỗi chuỗi cho dù ngắn đến đâu cũng sẽ khá lãng phí!

Có một vấn đề khác - hãy nhớ rằng C không hướng đối tượng, vì vậy nếu bạn thực hiện tiền tố dài tất cả các chuỗi, thì nó sẽ phải được định nghĩa là loại nội tại của trình biên dịch, không phải là a char*. Nếu đó là một loại đặc biệt, thì bạn sẽ không thể so sánh một chuỗi với một chuỗi không đổi, tức là:

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

sẽ phải có các chi tiết trình biên dịch đặc biệt để chuyển đổi chuỗi tĩnh đó thành Chuỗi hoặc có các hàm chuỗi khác nhau để tính đến tiền tố độ dài.

Cuối cùng tôi nghĩ, họ chỉ không chọn cách tiền tố dài không giống như nói Pascal.


10
Kiểm tra giới hạn cũng mất thời gian. Tầm thường trong các điều khoản ngày nay, nhưng một cái gì đó mọi người chú ý đến khi họ quan tâm khoảng 4 byte.
Steven Burnap

18
@StevenBurnap: ngày nay nó không tầm thường nếu bạn ở trong một vòng lặp bên trong vượt qua mọi pixel của hình ảnh 200 MB. Nói chung, nếu bạn đang viết C, bạn muốn đi nhanh và bạn không muốn lãng phí thời gian trong một kiểm tra ràng buộc vô dụng ở mỗi lần lặp khi forvòng lặp của bạn đã được thiết lập để tôn trọng các ranh giới.
Matteo Italia

4
@ VF1 "trở lại trong ngày" cũng có thể là hai byte (DEC PDP / 11 có ai không?)
ClickRick

7
Nó không chỉ là "trở lại trong ngày". Phần mềm dành cho C được nhắm mục tiêu là "ngôn ngữ lắp ráp di động" như hạt nhân hệ điều hành, trình điều khiển thiết bị, phần mềm nhúng thời gian thực, v.v. lãng phí một nửa tá hướng dẫn về kiểm tra giới hạn là vấn đề, và, trong nhiều trường hợp bạn cần phải "vượt ra khỏi giới hạn" (làm thế nào bạn có thể viết trình gỡ lỗi nếu bạn không thể truy cập ngẫu nhiên vào bộ lưu trữ chương trình khác?).
James Anderson

3
Đây thực sự là một đối số khá yếu khi xem xét rằng BCPL có các đối số được tính dài. Giống như Pascal mặc dù chỉ giới hạn ở 1 từ nên thường chỉ có 8 hoặc 9 bit, điều này hơi hạn chế (nó cũng loại trừ khả năng chia sẻ các phần của chuỗi, mặc dù thời gian tối ưu hóa có thể quá tiến bộ). Và khai báo một chuỗi là một cấu trúc có độ dài theo sau là mảng thực sự sẽ không cần hỗ trợ trình biên dịch đặc biệt ..
Voo

11

Trong C, bất kỳ tập hợp con liền kề nào của một mảng cũng là một mảng và có thể được vận hành như vậy. Điều này áp dụng cho cả hoạt động đọc và viết. Thuộc tính này sẽ không giữ nếu kích thước được lưu trữ rõ ràng.


6
"Thiết kế sẽ khác" không phải là lý do để thiết kế khác biệt.
VF1

7
@ VF1: Bạn đã bao giờ lập trình trong Standard Pascal chưa? Khả năng linh hoạt hợp lý của C với các mảng là một cải tiến lớn so với lắp ráp (không có gì an toàn) và thế hệ ngôn ngữ an toàn đầu tiên (an toàn quá mức, bao gồm cả giới hạn mảng chính xác)
MSalters

5
Khả năng cắt một mảng này thực sự là một đối số lớn cho thiết kế C89.

Các tin tặc Fortran trường học cũ cũng ma [dk] e sử dụng tốt tài sản này (mặc dù, nó yêu cầu chuyển các lát cắt đến một mảng trong Fortran). Nhầm lẫn và đau đớn để lập trình hoặc gỡ lỗi, nhưng nhanh chóng và thanh lịch khi làm việc.
dmckee

3
Có một cách thay thế thiết kế thú vị cho phép cắt: Không lưu trữ độ dài dọc theo các mảng. Đối với bất kỳ con trỏ tới một mảng, lưu trữ độ dài với con trỏ. (Khi bạn chỉ có một mảng C thực, kích thước là hằng số thời gian biên dịch và có sẵn cho trình biên dịch.) Nó tốn nhiều không gian hơn, nhưng cho phép cắt trong khi duy trì độ dài. Rust làm điều này cho các &[T]loại, ví dụ.

8

Vấn đề lớn nhất với việc các mảng được gắn thẻ với độ dài của chúng không phải là không gian cần thiết để lưu trữ độ dài đó, cũng không phải là câu hỏi về cách lưu trữ (sử dụng thêm một byte cho các mảng ngắn thường không bị phản đối, cũng không sử dụng bốn thêm byte cho mảng dài, nhưng sử dụng bốn byte ngay cả đối với mảng ngắn có thể). Một vấn đề lớn hơn nhiều là mã đã cho như:

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

cách duy nhất mà mã có thể chấp nhận cuộc gọi đầu tiên đến ClearTwoElementsnhưng từ chối cuộc gọi thứ hai là ClearTwoElementsphương thức nhận đủ thông tin để biết rằng trong mỗi trường hợp, nó nhận được một tham chiếu đến một phần của mảng foongoài việc biết phần nào. Điều đó thường sẽ tăng gấp đôi chi phí chuyển các tham số con trỏ. Hơn nữa, nếu mỗi mảng được dẫn trước bởi một con trỏ đến một địa chỉ vừa qua cuối (định dạng hiệu quả nhất để xác thực), mã được tối ưu hóa ClearTwoElementscó thể sẽ trở thành một cái gì đó như:

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

Lưu ý rằng, nói chung, một người gọi phương thức có thể hoàn toàn hợp pháp chuyển một con trỏ đến đầu mảng hoặc phần tử cuối cùng cho một phương thức; chỉ khi phương thức cố gắng truy cập các phần tử nằm ngoài mảng truyền vào thì các con trỏ như vậy mới gây ra sự cố. Do đó, trước tiên, một phương thức được gọi sẽ phải đảm bảo mảng đủ lớn để số học con trỏ xác thực các đối số của nó sẽ không vượt quá giới hạn và sau đó thực hiện một số tính toán con trỏ để xác thực các đối số. Thời gian dành cho việc xác nhận như vậy có thể sẽ vượt quá chi phí dành cho bất kỳ công việc thực tế nào. Hơn nữa, phương pháp có thể có hiệu quả hơn nếu nó được viết và gọi:

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

Khái niệm về một loại kết hợp một cái gì đó để xác định một đối tượng với một cái gì đó để xác định một mảnh của nó là một loại tốt. Tuy nhiên, một con trỏ kiểu C sẽ nhanh hơn nếu không cần thiết phải thực hiện xác nhận.


Nếu các mảng có kích thước thời gian chạy, thì con trỏ tới mảng sẽ khác về cơ bản từ con trỏ đến một thành phần của mảng. Latter có thể không thể chuyển đổi trực tiếp thành cũ (mà không tạo mảng mới). []cú pháp có thể vẫn tồn tại cho các con trỏ, nhưng nó sẽ khác với các mảng "thực" giả định này và vấn đề bạn mô tả có thể sẽ không tồn tại.
hyde

@hyde: Câu hỏi là liệu số học có nên được cho phép trên các con trỏ có địa chỉ cơ sở đối tượng không xác định. Ngoài ra, tôi quên một khó khăn khác: mảng trong các cấu trúc. Nghĩ về nó, tôi không chắc sẽ có loại con trỏ nào có thể trỏ đến một mảng được lưu trữ trong một cấu trúc, mà không yêu cầu mỗi con trỏ không chỉ bao gồm địa chỉ của con trỏ, mà còn cả pháp lý trên và dưới phạm vi nó có thể truy cập.
supercat

Điểm xen kẽ. Tuy nhiên, tôi nghĩ rằng điều này vẫn làm giảm câu trả lời của amon.
VF1

Câu hỏi hỏi về mảng. Con trỏ là địa chỉ bộ nhớ và sẽ không thay đổi với tiền đề của câu hỏi, theo như hiểu được ý định. Mảng sẽ có chiều dài, con trỏ sẽ không thay đổi (ngoại trừ con trỏ tới mảng sẽ cần phải là một kiểu mới, riêng biệt, duy nhất, giống như con trỏ tới struct).
hyde

@hyde: Nếu một trong những thay đổi đủ ngữ nghĩa của ngôn ngữ, có thể có các mảng bao gồm một độ dài liên quan, mặc dù các mảng được lưu trữ trong các cấu trúc sẽ gây ra một số khó khăn. Với ngữ nghĩa như hiện tại, kiểm tra giới hạn mảng sẽ chỉ hữu ích nếu việc kiểm tra tương tự được áp dụng cho các con trỏ cho các phần tử mảng.
supercat

7

Một trong những khác biệt cơ bản giữa C và hầu hết các ngôn ngữ thế hệ thứ 3 khác, và tất cả các ngôn ngữ gần đây mà tôi biết, là C không được thiết kế để giúp cuộc sống dễ dàng hơn hoặc an toàn hơn cho lập trình viên. Nó được thiết kế với mong muốn lập trình viên biết họ đang làm gì và muốn làm chính xác và chỉ có thế. Nó không làm bất cứ điều gì 'đằng sau hậu trường' để bạn không gặp phải bất ngờ nào. Ngay cả tối ưu hóa mức biên dịch là tùy chọn (trừ khi bạn sử dụng trình biên dịch Microsoft).

Nếu một lập trình viên muốn viết giới hạn kiểm tra mã của họ, C làm cho nó đủ đơn giản để làm điều đó, nhưng lập trình viên phải chọn trả giá tương ứng về không gian, độ phức tạp và hiệu suất. Mặc dù tôi đã không sử dụng nó trong sự tức giận trong nhiều năm, tôi vẫn sử dụng nó khi dạy lập trình để vượt qua khái niệm về việc ra quyết định dựa trên ràng buộc. Về cơ bản, điều đó có nghĩa là bạn có thể chọn làm bất cứ điều gì bạn muốn, nhưng mọi quyết định bạn đưa ra đều có một mức giá mà bạn cần phải biết. Điều này càng trở nên quan trọng hơn khi bạn bắt đầu nói với người khác những gì bạn muốn chương trình của họ làm.


3
C không được "thiết kế" nhiều như vậy khi nó phát triển. Ban đầu, một tuyên bố như int f[5];sẽ không tạo ra fnhư một mảng năm mục; thay vào đó, nó tương đương với int CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;. Khai báo trước có thể được xử lý mà trình biên dịch phải thực sự "hiểu" thời gian mảng; nó chỉ đơn giản là phải đưa ra một lệnh biên dịch hợp ngữ để phân bổ không gian và sau đó có thể quên rằng fđã từng có bất cứ điều gì liên quan đến một mảng. Các hành vi không nhất quán của các loại mảng bắt nguồn từ điều này.
supercat

1
Hóa ra là không có lập trình viên nào biết họ đang làm gì ở mức độ mà C yêu cầu.
CodeInChaos

7

Câu trả lời ngắn:

Bởi vì C là ngôn ngữ lập trình cấp thấp , nó hy vọng bạn sẽ tự xử lý các vấn đề này, nhưng điều này sẽ tăng tính linh hoạt cao hơn trong chính xác cách bạn thực hiện nó.

C có một khái niệm thời gian biên dịch của một mảng được khởi tạo với độ dài nhưng trong thời gian chạy, toàn bộ mọi thứ chỉ được lưu trữ dưới dạng một con trỏ duy nhất để bắt đầu dữ liệu. Nếu bạn muốn truyền chiều dài mảng cho một hàm cùng với mảng, bạn tự làm điều đó:

retval = my_func(my_array, my_array_length);

Hoặc bạn có thể sử dụng một cấu trúc với một con trỏ và chiều dài, hoặc bất kỳ giải pháp nào khác.

Một ngôn ngữ cấp cao hơn sẽ làm điều này cho bạn như là một phần của kiểu mảng của nó. Trong C, bạn được giao trách nhiệm tự làm việc này, nhưng cũng có thể linh hoạt chọn cách thực hiện. nếu tất cả các mã bạn đang viết đã biết độ dài của mảng, bạn không cần phải vượt qua độ dài xung quanh dưới dạng một biến.

Hạn chế rõ ràng là không có giới hạn vốn có kiểm tra các mảng được truyền xung quanh dưới dạng con trỏ, bạn có thể tạo một số mã nguy hiểm nhưng đó là bản chất của ngôn ngữ hệ thống / cấp độ thấp và sự đánh đổi mà chúng đưa ra.


1
+1 "Và nếu tất cả các mã bạn đang viết đã biết độ dài của mảng, bạn không cần phải vượt qua độ dài xung quanh dưới dạng một biến."
林果

Nếu chỉ con trỏ + chiều dài struct đã được đưa vào thư viện ngôn ngữ và tiêu chuẩn. Vì vậy, nhiều lỗ hổng bảo mật có thể tránh được.
CodeInChaos

Sau đó, nó sẽ không thực sự là C. Có những ngôn ngữ khác làm điều đó. C giúp bạn có trình độ thấp.
thomasrutter

C được phát minh như một ngôn ngữ lập trình cấp thấp và nhiều phương ngữ vẫn hỗ trợ lập trình cấp thấp, nhưng nhiều tác giả biên dịch ủng hộ các phương ngữ không thực sự được gọi là ngôn ngữ cấp thấp. Chúng cho phép và thậm chí yêu cầu cú pháp cấp thấp, nhưng sau đó cố gắng suy ra các cấu trúc cấp cao hơn mà hành vi của chúng có thể không khớp với ngữ nghĩa được ngụ ý bởi cú pháp.
supercat

5

Vấn đề lưu trữ thêm là một vấn đề, nhưng theo tôi là một vấn đề nhỏ. Rốt cuộc, hầu hết thời gian bạn sẽ cần theo dõi độ dài dù thế nào, mặc dù amon đã đưa ra một điểm tốt là nó thường có thể được theo dõi tĩnh.

Một vấn đề lớn hơn là nơi lưu trữ chiều dài và thời gian thực hiện. Không có một nơi nào hoạt động trong mọi tình huống. Bạn có thể nói chỉ lưu trữ độ dài trong bộ nhớ ngay trước dữ liệu. Điều gì xảy ra nếu mảng không trỏ đến bộ nhớ, nhưng một cái gì đó giống như bộ đệm UART?

Thoát khỏi độ dài cho phép lập trình viên tạo ra sự trừu tượng của riêng mình cho tình huống thích hợp và có rất nhiều thư viện đã sẵn sàng cho trường hợp mục đích chung. Câu hỏi thực sự là tại sao những thứ trừu tượng đó không được sử dụng trong các ứng dụng nhạy cảm với bảo mật?


1
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?Bạn có thể vui lòng giải thích điều này một chút nữa không? Ngoài ra, một cái gì đó có thể xảy ra quá thường xuyên hoặc đó chỉ là một trường hợp hiếm gặp?
Mahdi

Nếu tôi đã thiết kế nó, một đối số hàm được viết là T[]sẽ không tương đương với T*mà chỉ truyền một bộ con trỏ và kích thước cho hàm. Các mảng kích thước cố định có thể phân rã thành một mảng mảng như vậy, thay vì phân rã thành các con trỏ như trong C. Ưu điểm chính của phương pháp này không phải là nó an toàn mà là một quy ước mà mọi thứ, kể cả thư viện chuẩn có thể xây dựng.
CodeInChaos

1

Từ sự phát triển của ngôn ngữ C :

Các cấu trúc, dường như, ánh xạ một cách trực quan vào bộ nhớ trong máy, nhưng trong một cấu trúc có chứa một mảng, không có nơi nào tốt để bỏ con trỏ chứa cơ sở của mảng, cũng không có cách nào thuận tiện để sắp xếp nó. khởi tạo. Ví dụ, các mục nhập thư mục của các hệ thống Unix đầu tiên có thể được mô tả trong C là
struct {
    int inumber;
    char    name[14];
};
Tôi muốn cấu trúc không chỉ đơn thuần là để mô tả một đối tượng trừu tượng mà còn để mô tả một tập hợp các bit có thể được đọc từ một thư mục. Trình biên dịch có thể ẩn con trỏ đến namengữ nghĩa yêu cầu ở đâu? Ngay cả khi các cấu trúc được cho là trừu tượng hơn và không gian cho con trỏ có thể bị ẩn đi bằng cách nào đó, làm thế nào tôi có thể xử lý vấn đề kỹ thuật khởi tạo đúng các con trỏ này khi phân bổ một đối tượng phức tạp, có lẽ là một cấu trúc chỉ định các mảng chứa cấu trúc có độ sâu tùy ý?

Giải pháp cấu thành bước nhảy quan trọng trong chuỗi tiến hóa giữa BCPL không chữ và gõ C. Nó đã loại bỏ sự vật chất hóa của con trỏ trong lưu trữ, và thay vào đó gây ra việc tạo con trỏ khi tên mảng được đề cập trong một biểu thức. Quy tắc, tồn tại trong C ngày nay, là các giá trị của kiểu mảng được chuyển đổi, khi chúng xuất hiện trong biểu thức, thành con trỏ đến đầu tiên của các đối tượng tạo thành mảng.

Đoạn văn đó giải quyết tại sao các biểu thức mảng phân rã thành con trỏ trong hầu hết các trường hợp, nhưng lý do tương tự áp dụng cho lý do tại sao độ dài mảng không được lưu trữ với chính mảng đó; nếu bạn muốn ánh xạ một-một giữa định nghĩa kiểu và biểu diễn của nó trong bộ nhớ (như Ritchie đã làm), thì không có nơi nào tốt để lưu trữ siêu dữ liệu đó.

Ngoài ra, hãy suy nghĩ về mảng đa chiều; nơi bạn sẽ lưu trữ siêu dữ liệu độ dài cho mỗi thứ nguyên để bạn vẫn có thể đi qua mảng với thứ gì đó như

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );

-2

Câu hỏi giả định rằng có các mảng trong C. Không có. Những thứ được gọi là mảng chỉ là một đường cú pháp cho các hoạt động trên chuỗi liên tục của dữ liệu và con trỏ mỹ phẩm.

Đoạn mã sau sao chép một số dữ liệu từ src sang dst trong các đoạn có kích thước int mà không biết rằng đó thực sự là chuỗi ký tự.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

Tại sao C đơn giản đến mức nó không có mảng thích hợp? Tôi không biết câu trả lời chính xác cho câu hỏi mới này. Nhưng một số người thường nói rằng C chỉ là (phần nào) dễ đọc hơn và trình biên dịch di động.


2
Tôi không nghĩ bạn đã trả lời câu hỏi.
Robert Harvey

2
Những gì bạn nói là đúng, nhưng người hỏi muốn biết tại sao lại như vậy.

9
Hãy nhớ rằng, một trong những biệt danh của C là "lắp ráp di động". Mặc dù các phiên bản mới hơn của tiêu chuẩn đã bổ sung các khái niệm cấp cao hơn, nhưng ở cốt lõi, nó bao gồm các cấu trúc và hướng dẫn cấp thấp đơn giản phổ biến trên hầu hết các máy không tầm thường. Điều này thúc đẩy hầu hết các quyết định thiết kế được thực hiện bằng ngôn ngữ. Các biến duy nhất tồn tại trong thời gian chạy là số nguyên, số float và con trỏ. Hướng dẫn bao gồm số học, so sánh và nhảy. Khá nhiều thứ khác là một lớp mỏng được xây dựng trên đó.

8
Thật sai khi nói C không có mảng, xem xét cách bạn thực sự không thể tạo cùng một nhị phân với các cấu trúc khác (tốt, ít nhất là không nếu bạn xem xét việc sử dụng #defines để xác định kích thước mảng). Mảng trong C "chuỗi dữ liệu liên tục", không có gì có liên quan đến nó. Sử dụng các con trỏ như chúng là mảng là đường cú pháp ở đây (thay vì số học con trỏ rõ ràng), chứ không phải mảng.
hyde

2
Có, hãy xem xét mã này : struct Foo { int arr[10]; }. arrlà một mảng, không phải là một con trỏ.
Steven Burnap
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.