Chỉ số mảng âm có được phép trong C không?


115

Tôi chỉ đang đọc một số mã và thấy rằng người đó đang sử dụng arr[-2]để truy cập phần tử thứ 2 trước arr, như sau:

|a|b|c|d|e|f|g|
       ^------------ arr[0]
         ^---------- arr[1]
   ^---------------- arr[-2]

Điều đó có được phép không?

Tôi biết điều đó arr[x]cũng giống như *(arr + x). Vì vậy, arr[-2]*(arr - 2), mà dường như OK. Bạn nghĩ sao?

Câu trả lời:


168

Đúng rồi. Từ C99 §6.5.2.1 / 2:

Ý nghĩa của toán tử chỉ số con [] là E1 [E2] giống hệt với (* ((E1) + (E2))).

Không có ma thuật. Đó là tương đương 1-1. Như mọi khi khi tham chiếu đến một con trỏ (*), bạn cần đảm bảo rằng nó đang trỏ đến một địa chỉ hợp lệ.


2
Cũng lưu ý rằng bạn không cần phải tham khảo con trỏ để lấy UB. Chỉ tính toán somearray-2là không xác định trừ khi kết quả nằm trong phạm vi từ đầu somearrayđến 1 sau khi kết thúc.
RBerteig

34
Trong những cuốn sách cũ, nó []được tham chiếu như một đường cú pháp cho số học con trỏ. Cách yêu thích để gây nhầm lẫn cho người mới bắt đầu là viết 1[arr]- thay vì arr[1]- và xem họ đoán ý nghĩa của điều đó.
Dummy00001,

4
Điều gì xảy ra trên hệ thống 64 bit (LP64) khi bạn có chỉ số int 32 bit là âm? Chỉ mục có nên được thăng cấp thành một int có dấu 64 bit trước khi tính toán địa chỉ không?
Paul R

4
@Paul, từ §6.5.6 / 8 (Toán tử cộng), "Khi một biểu thức có kiểu số nguyên được thêm vào hoặc trừ khỏi một con trỏ, kết quả có kiểu toán hạng con trỏ. Nếu toán hạng con trỏ trỏ đến một phần tử của một đối tượng mảng và mảng đủ lớn, kết quả trỏ đến một phần tử khác với phần tử ban đầu sao cho sự khác biệt của các chỉ số con của các phần tử mảng kết quả và ban đầu bằng biểu thức số nguyên. " Vì vậy, tôi nghĩ rằng nó sẽ được thăng cấp, và ((E1)+(E2))sẽ là một con trỏ (64-bit) với giá trị mong đợi.
Matthew Flaschen.

@Matthew: cảm ơn vì điều đó - có vẻ như nó sẽ hoạt động như người ta có thể mong đợi một cách hợp lý.
Paul R

63

Điều này chỉ hợp lệ nếu arrlà một con trỏ trỏ đến phần tử thứ hai trong một mảng hoặc một phần tử sau đó. Nếu không, nó không hợp lệ, vì bạn sẽ truy cập bộ nhớ bên ngoài giới hạn của mảng. Vì vậy, ví dụ, điều này sẽ là sai:

int arr[10];

int x = arr[-2]; // invalid; out of range

Nhưng điều này sẽ ổn:

int arr[10];
int* p = &arr[2];

int x = p[-2]; // valid:  accesses arr[0]

Tuy nhiên, việc sử dụng chỉ số phụ phủ định là không bình thường.


Tôi sẽ không đi xa như vậy để nói nó là không hợp lệ, chỉ có khả năng lộn xộn
Matt Joiner

13
@Matt: Mã trong ví dụ đầu tiên mang lại hành vi không xác định.
James McNellis

5
Nó không hợp lệ. Theo tiêu chuẩn C, nó rõ ràng có hành vi không xác định. Mặt khác, nếu int arr[10];là một phần của một cấu trúc với các yếu tố khác trước đó, arr[-2]có thể có khả năng được xác định rõ, và bạn có thể xác định nếu nó được dựa trên offsetofvv
R .. GitHub DỪNG GIÚP ICE

4
Tìm thấy nó trong K&R Phần 5.3, gần cuối: If one is sure that the elements exist, it is also possible to index backwards in an array; p[-1], p[-2], and so on are syntactically legal, and refer to the elements that immediately precede p[0]. Of course, it is illegal to refer to objects that are not within the array bounds.Tuy nhiên, ví dụ của bạn tốt hơn để giúp tôi hiểu nó. Cảm ơn!
Qiang Xu

4
Xin lỗi vì sự thích hợp của chủ đề, nhưng tôi chỉ thích cách K&R mơ hồ về "bất hợp pháp" nghĩa là gì. Câu cuối cùng làm cho nó có vẻ như truy cập ngoài giới hạn gây ra lỗi biên dịch. Cuốn sách đó là thuốc độc cho người mới bắt đầu.
Martin

12

Nghe có vẻ ổn với tôi. Tuy nhiên, nó sẽ là một trường hợp hiếm hoi mà bạn cần nó một cách hợp pháp.


9
Nó không phải hiếm - nó rất hữu ích trong việc xử lý ảnh với các toán tử lân cận.
Paul R

Tôi chỉ cần sử dụng điều này vì tôi đang tạo một nhóm bộ nhớ với một ngăn xếp và đống [cấu trúc / thiết kế]. Ngăn xếp phát triển theo hướng địa chỉ bộ nhớ cao hơn, đống phát triển theo địa chỉ bộ nhớ thấp hơn. Họp mặt giữa chừng.
JMI MADISON

8

Điều có thể là arrđã trỏ đến giữa mảng, do đó làm cho việc arr[-2]trỏ đến một thứ gì đó trong mảng ban đầu mà không đi ra ngoài giới hạn.


7

Tôi không chắc điều này đáng tin cậy đến mức nào, nhưng tôi vừa đọc thông báo sau về chỉ số mảng âm trên hệ thống 64 bit (có lẽ là LP64): http://www.devx.com/tips/Tip/41349

Tác giả dường như đang nói rằng chỉ số mảng int 32 bit với địa chỉ 64 bit có thể dẫn đến tính toán địa chỉ xấu trừ khi chỉ số mảng được thăng cấp rõ ràng lên 64 bit (ví dụ: thông qua một ptrdiff_t cast). Tôi thực sự đã thấy một lỗi bản chất của anh ấy với phiên bản PowerPC của gcc 4.1.0, nhưng tôi không biết đó là lỗi trình biên dịch (tức là phải hoạt động theo tiêu chuẩn C99) hay hành vi chính xác (tức là chỉ mục cần truyền thành 64 bit cho hành vi đúng)?


3
Điều này nghe giống như một lỗi trình biên dịch.
tbleher 13/09/13

2

Tôi biết câu hỏi đã được trả lời, nhưng tôi không thể cưỡng lại việc chia sẻ lời giải thích này.

Tôi nhớ Nguyên tắc thiết kế trình biên dịch, Giả sử a là một mảng int và kích thước của int là 2, & Địa chỉ cơ sở cho a là 1000.

Làm thế nào a[5]sẽ hoạt động ->

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Giải thích này cũng là lý do tại sao các chỉ mục âm trong mảng hoạt động trong C.

tức là nếu tôi truy cập a[-5]nó sẽ cho tôi

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Nó sẽ trả về cho tôi đối tượng tại vị trí 990. Theo logic này, chúng ta có thể truy cập các chỉ mục âm trong Array in C.


2

Về lý do tại sao ai đó muốn sử dụng chỉ mục phủ định, tôi đã sử dụng chúng trong hai ngữ cảnh:

  1. Có một bảng các số tổ hợp cho bạn biết comb [1] [- 1] = 0; bạn luôn có thể kiểm tra các chỉ mục trước khi truy cập vào bảng, nhưng bằng cách này, mã trông sạch hơn và thực thi nhanh hơn.

  2. Đặt một centinel ở đầu bảng. Ví dụ: bạn muốn sử dụng một cái gì đó như

     while (x < a[i]) i--;

nhưng sau đó bạn cũng nên kiểm tra xem đó ilà tích cực.
Giải pháp: làm cho nó để a[-1]-DBLE_MAX, do đó x&lt;a[-1]sẽ luôn luôn là sai.


0
#include <stdio.h>

int main() // negative index
{ 
    int i = 1, a[5] = {10, 20, 30, 40, 50};
    int* mid = &a[5]; //legal;address,not element there
    for(; i < 6; ++i)
    printf(" mid[ %d ] = %d;", -i, mid[-i]);
}

1
Mặc dù mã này có thể trả lời câu hỏi, nhưng việc cung cấp thêm ngữ cảnh liên quan đến lý do và / hoặc cách mã này trả lời câu hỏi sẽ cải thiện giá trị lâu dài của nó.
β.εηοιτ.βε

Python groovy ... có chúng. Trường hợp sử dụng đơn giản là người ta có thể truy cập phần tử cuối cùng của mảng mà không cần biết kích thước mảng, một yêu cầu rất thực tế trong nhiều tình huống của Dự án. Cũng có nhiều DSL được hưởng lợi từ điều này.
Rathinavelu Muthaliar
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.