Tại sao các chỉ số mảng âm có ý nghĩa?


14

Tôi đã bắt gặp một kinh nghiệm kỳ lạ trong lập trình C. Xem xét mã này:

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

Khi tôi biên dịch và chạy nó, tôi không nhận được bất kỳ lỗi hay cảnh báo nào. Như giảng viên của tôi đã nói, chỉ số mảng -1truy cập vào một biến khác. Tôi vẫn còn bối rối, tại sao một ngôn ngữ lập trình lại có khả năng này? Ý tôi là, tại sao cho phép các chỉ số mảng âm?


2
Trong khi câu hỏi này được thúc đẩy với C là ngôn ngữ lập trình cụ thể, tôi nghĩ nó có thể được hiểu là một câu hỏi mang tính khái niệm ở đây (nếu không có).
Raphael

6
@Raphael Tôi không đồng ý và tin rằng nó nên thuộc về SO, dù đây là hành vi không xác định trong sách giáo khoa (bộ nhớ tham chiếu bên ngoài mảng) và các cờ biên dịch thích hợp sẽ cảnh báo về điều này
ratchet freak

Tôi đồng ý với @ratchetfreak. Nó dường như là một lỗ hổng trình biên dịch vì phạm vi chỉ mục hợp lệ là [0, 5]. Bất cứ điều gì bên ngoài phải là một lỗi biên dịch / thời gian chạy. Nói chung, vectơ là trường hợp cụ thể của các hàm có chỉ số phần tử đầu tiên tùy thuộc vào người dùng. Vì hợp đồng C là các phần tử bắt đầu ở chỉ số 0, nên việc truy cập các phần tử âm là lỗi.
Val

2
@Raphael C có hai đặc thù so với các ngôn ngữ điển hình với các mảng quan trọng ở đây. Một là C có các phân đoạn và tham chiếu đến phần tử -1của một phân đoạn là một cách hoàn toàn hợp lệ để tham chiếu đến phần tử trước mảng đó trong mảng lớn hơn. Mặt khác là nếu chỉ mục không hợp lệ, chương trình không hợp lệ, nhưng trong hầu hết các triển khai, bạn sẽ nhận được hành vi xấu im lặng, không phải là lỗi ngoài phạm vi.
Gilles 'SO- ngừng trở nên xấu xa'

4
@Gilles Nếu đó là điểm chính của câu hỏi, thì điều này thực sự đã có trên Stack Overflow .
Raphael

Câu trả lời:


27

Hoạt động lập chỉ mục mảng a[i]đạt được ý nghĩa từ các tính năng sau của C

  1. Cú pháp a[i]tương đương với *(a + i). Vì vậy, nó là hợp lệ để nói 5[a]để có được ở phần tử thứ 5 của a.

  2. Con trỏ số học nói rằng đã cho một con trỏ pvà một số nguyên i, p + i con trỏ được pnâng cao bởi i * sizeof(*p)byte

  3. Tên của một mảng arất nhanh bị phá hủy thành một con trỏ đến phần tử thứ 0 củaa

Trong thực tế, lập chỉ mục mảng là một trường hợp đặc biệt của lập chỉ mục con trỏ. Kể từ khi một con trỏ có thể trỏ đến bất cứ nơi nào bên trong một mảng, bất kỳ biểu hiện độc đoán rằng trông giống như p[-1]không sai bằng việc kiểm tra, và do đó trình biên dịch không (không thể) xem xét tất cả các biểu hiện như lỗi.

Ví dụ của bạn a[-1], nơi athực sự là tên của một mảng thực sự không hợp lệ. IIRC, nó là undefined nếu có một giá trị con trỏ có ý nghĩa như là kết quả của biểu thức a - 1alà biết là một con trỏ tới phần tử 0 của một mảng. Vì vậy, một trình biên dịch thông minh có thể phát hiện ra điều này và đánh dấu nó là một lỗi. Các trình biên dịch khác vẫn có thể tuân thủ trong khi cho phép bạn tự bắn vào chân mình bằng cách đưa cho bạn một con trỏ tới một khe ngăn xếp ngẫu nhiên.

Câu trả lời của khoa học máy tính là:

  • Trong C, []toán tử được định nghĩa trên các con trỏ, không phải mảng. Cụ thể, nó được định nghĩa theo thuật ngữ số học con trỏ và con trỏ.

  • Trong C, một con trỏ trừu tượng là một tuple (start, length, offset)với điều kiện đó 0 <= offset <= length. Số học con trỏ về cơ bản là nâng số học trên phần bù, với lời cảnh báo rằng nếu kết quả của hoạt động vi phạm điều kiện con trỏ, thì đó là một giá trị không xác định. Bỏ tham chiếu một con trỏ thêm một ràng buộc bổ sung đó offset < length.

  • C có một khái niệm undefined behaviourcho phép trình biên dịch biểu diễn cụ thể bộ dữ liệu đó dưới dạng một số duy nhất và không phải phát hiện bất kỳ vi phạm nào về điều kiện con trỏ. Bất kỳ chương trình nào thỏa mãn ngữ nghĩa trừu tượng sẽ được an toàn với ngữ nghĩa cụ thể (mất mát). Bất cứ điều gì vi phạm ngữ nghĩa trừu tượng đều có thể, không cần bình luận, được trình biên dịch chấp nhận và nó có thể làm bất cứ điều gì nó muốn làm với nó.


Vui lòng cố gắng đưa ra một câu trả lời chung chung, không phải một câu trả lời tùy thuộc vào đặc điểm riêng của bất kỳ ngôn ngữ lập trình cụ thể nào.
Raphael

5
@Raphael, câu hỏi rõ ràng về C. Tôi nghĩ rằng tôi đã giải quyết câu hỏi cụ thể về lý do tại sao trình biên dịch C được phép biên dịch một biểu thức dường như vô nghĩa trong định nghĩa của C.
Hari

Các câu hỏi về C nói riêng là không chính thức ở đây; lưu ý nhận xét của tôi về câu hỏi.
Raphael

4
Tôi tin rằng khía cạnh ngôn ngữ học so sánh của câu hỏi vẫn còn hữu ích. Tôi tin rằng tôi đã đưa ra một mô tả có hương vị khá "khoa học máy tính" về lý do tại sao một triển khai cụ thể thể hiện một ngữ nghĩa cụ thể cụ thể.
Hari

15

Mảng đơn giản được đặt ra như những khối ký ức liền kề nhau. Một truy cập mảng như [i] được chuyển đổi thành quyền truy cập vào vị trí bộ nhớ addressOf (a) + i. Mã a[-1]này là hoàn toàn dễ hiểu, nó chỉ đơn giản đề cập đến địa chỉ một trước khi bắt đầu mảng.

Điều này có vẻ điên rồ, nhưng có nhiều lý do tại sao điều này được cho phép:

  • thật tốn kém để kiểm tra xem chỉ số i đến [-] có nằm trong giới hạn của mảng hay không.
  • một số kỹ thuật lập trình thực sự khai thác thực tế a[-1]là hợp lệ. Chẳng hạn, nếu tôi biết đó akhông thực sự là điểm bắt đầu của mảng, mà là một con trỏ vào giữa mảng, thì a[-1]chỉ cần lấy phần tử của mảng nằm ở bên trái của con trỏ.

6
Nói cách khác, nó có lẽ không nên được sử dụng. Giai đoạn = Stage. Cái gì, tên của bạn là Donald Knuth và bạn cố gắng lưu 17 hướng dẫn khác? Bằng mọi cách, hãy tiếp tục.
Raphael

Cảm ơn đã trả lời, nhưng tôi không có ý tưởng. BTW Tôi sẽ đọc nó nhiều lần cho đến khi tôi hiểu .. :)
Mohammed Fawzan

2
@Raphael: Việc triển khai mô hình đối tượng cola sử dụng vị trí -1 để lưu trữ vtable: piumarta.com/software/cola/objmodel2.pdf . Do đó, các trường được lưu trữ trong phần tích cực của đối tượng và vtable trong phần âm. Tôi không thể nhớ các chi tiết, nhưng tôi nghĩ rằng đó là để làm với sự nhất quán.
Dave Clarke

@ DeZéroToxin: Một mảng thực sự chỉ là một vị trí trong bộ nhớ, với một số vị trí bên cạnh nó là một phần logic của mảng. Nhưng thực sự, một mảng chỉ là một con trỏ.
Dave Clarke

1
@Raphael, a[-1]có ý nghĩa hoàn hảo đối với một số trường hợp a, trong trường hợp cụ thể này là bất hợp pháp (nhưng không bị trình biên dịch bắt)
vonbrand

4

Như các câu trả lời khác giải thích, đây là hành vi không xác định trong C. Hãy xem xét rằng C đã được xác định (và chủ yếu được sử dụng) như là một "trình biên dịch cấp cao". Người dùng của C đánh giá nó vì tốc độ không khoan nhượng và việc kiểm tra nội dung trong thời gian chạy (phần lớn) không nằm trong câu hỏi vì hiệu suất tuyệt đối. Một số cấu trúc C trông vô nghĩa đối với những người đến từ các ngôn ngữ khác có ý nghĩa hoàn hảo trong C, như thế này a[-1]. Vâng, nó không phải lúc nào cũng có ý nghĩa (


1
Tôi thích câu trả lời này. Đưa ra một lý do thực sự cho lý do tại sao điều này là ổn.
darxsys

3

Người ta có thể sử dụng một tính năng như vậy để viết các phương thức cấp phát bộ nhớ truy cập trực tiếp vào bộ nhớ. Một cách sử dụng như vậy là kiểm tra khối bộ nhớ trước đó bằng chỉ số mảng âm để xác định xem hai khối có thể được hợp nhất hay không. Tôi đã sử dụng tính năng này khi tôi phát triển trình quản lý bộ nhớ không bay hơi.


2

C không được gõ mạnh. Một trình biên dịch C tiêu chuẩn sẽ không kiểm tra giới hạn mảng. Một điều khác là một mảng trong C không là gì ngoài một khối bộ nhớ liền kề và lập chỉ mục bắt đầu từ 0 nên chỉ số -1 là vị trí của bất kỳ mẫu bit nào trước đó a[0].

Các ngôn ngữ khác khai thác các chỉ số tiêu cực một cách tốt đẹp. Trong Python, a[-1]sẽ trả về phần tử cuối cùng, a[-2]sẽ trả về phần tử thứ hai đến cuối cùng, v.v.


2
Làm thế nào để gõ mạnh và chỉ số mảng liên quan? Có ngôn ngữ với một loại cho naturals trong đó các chỉ số mảng phải là naturals?
Raphael

@Raphael Theo tôi biết, gõ mạnh có nghĩa là lỗi loại được bắt. Một mảng là một loại, IndexOutOfBound là một lỗi vì vậy trong một ngôn ngữ được gõ mạnh, điều này sẽ được báo cáo, trong C điều này sẽ không. Ý tôi là thế
saadtaame

Trong các ngôn ngữ tôi biết, các chỉ mục mảng có kiểu int, a[-5]và, nói chung, int i; ... a[i] = ...;được gõ chính xác. Lỗi chỉ mục chỉ được phát hiện trong thời gian chạy. Tất nhiên, một trình biên dịch thông minh có thể phát hiện một số vi phạm.
Raphael

@Raphael Tôi đang nói về kiểu dữ liệu mảng nói chung, không phải kiểu chỉ mục. Điều đó giải thích tại sao C cho phép người dùng viết [-5]. Có, -5 là loại chỉ mục chính xác nhưng vượt quá giới hạn và đó là một lỗi. Không có đề cập đến kiểm tra kiểu biên dịch hoặc thời gian chạy trong câu trả lời của tôi.
saadtaame

1

Nói một cách đơn giản:

Tất cả các biến (bao gồm cả mảng) trong C được lưu trữ trong bộ nhớ. Giả sử bạn có 14 byte "bộ nhớ" và bạn khởi tạo các mục sau:

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

Ngoài ra, hãy xem xét kích thước của một int là 2 byte. Sau đó, theo giả thuyết, trong 2 byte đầu tiên của bộ nhớ, số nguyên a sẽ được lưu. Trong 2 byte tiếp theo, số nguyên của vị trí đầu tiên của mảng sẽ được lưu (có nghĩa là mảng [0]).

Sau đó, khi bạn nói mảng [-1] giống như tham chiếu đến số nguyên được lưu trong bộ nhớ ngay trước mảng [0], theo giả thuyết, số nguyên a của chúng tôi là số nguyên. Trong thực tế, đây không chính xác là cách các biến được lưu trữ trong bộ nhớ.


0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;

Chào mừng đến với CS.SE! Chúng tôi đang tìm kiếm câu trả lời đi kèm với lời giải thích hoặc mô tả về bài đọc. Chúng tôi không phải là một trang web mã hóa và chúng tôi không muốn câu trả lời chỉ là một khối mã. Bạn có thể xem xét liệu bạn có thể chỉnh sửa câu trả lời của mình để cung cấp loại thông tin đó hay không. Cảm ơn bạn!
DW
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.