Lấy địa chỉ của phần tử mảng one-past-the-end thông qua subscript: legal theo Chuẩn C ++ hay không?


77

Tôi đã thấy nó được khẳng định nhiều lần bây giờ rằng mã sau không được Tiêu chuẩn C ++ cho phép:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

&array[5]pháp C ++ mã trong bối cảnh này?

Tôi muốn một câu trả lời có tham chiếu đến Tiêu chuẩn nếu có thể.

Cũng sẽ rất thú vị nếu biết nó có đáp ứng tiêu chuẩn C. Và nếu nó không phải là C ++ tiêu chuẩn, tại sao quyết định xử lý nó khác với array + 5hoặc &array[4] + 1?


7
@Brian: Không, nó sẽ chỉ yêu cầu kiểm tra giới hạn nếu thời gian chạy được yêu cầu để bắt lỗi. Để tránh điều đó, tiêu chuẩn có thể nói đơn giản là "không được phép". Đó là hành vi không xác định ở mức tốt nhất. Bạn không được phép làm điều đó và thời gian chạy và trình biên dịch không bắt buộc phải cho bạn biết nếu bạn làm điều đó.
jalf

Ok, chỉ để làm rõ một chút, bởi vì tiêu đề đã đánh lừa tôi: Một con trỏ ở phía trước cuối của một mảng không nằm ngoài giới hạn. Nói chung, con trỏ nằm ngoài giới hạn không được phép, nhưng tiêu chuẩn này khoan dung hơn rất nhiều với con trỏ một quá khứ. Bạn có thể muốn chỉnh sửa tiêu đề nếu bạn đang hỏi cụ thể về các con trỏ một quá khứ. Nếu bạn muốn biết về con trỏ ngoài giới hạn nói chung , bạn nên chỉnh sửa ví dụ của mình. ;)
jalf

Anh ấy không hỏi về quá khứ nói chung. Anh ấy đang hỏi về việc sử dụng toán tử & để lấy con trỏ.
Matthew Flaschen

1
@Matthew: Nhưng câu trả lời cho điều đó phụ thuộc vào nơi con trỏ đó trỏ đến. Bạn được phép sử dụng địa chỉ trong trường hợp một quá khứ, nhưng không phải trong trường hợp vượt quá giới hạn.
jalf

Phần 5.3.1.1 Toán tử một ngôi '*': 'kết quả là một giá trị tham chiếu đến đối tượng hoặc hàm'. Phần 5.2.1 Ký hiệu biểu thức E1 [E2] giống hệt nhau (theo định nghĩa) với * ((E1) + (E2)). Bằng cách đọc của tôi về tiêu chuẩn ở đây. Không có sự tái cấp nguồn của con trỏ kết quả. (xem giải thích đầy đủ bên dưới)
Martin York

Câu trả lời:


40

Ví dụ của bạn là hợp pháp, nhưng chỉ vì bạn không thực sự sử dụng con trỏ nằm ngoài giới hạn.

Trước tiên, hãy xử lý con trỏ ngoài giới hạn (vì đó là cách tôi giải thích câu hỏi của bạn ban đầu, trước khi tôi nhận thấy rằng ví dụ này sử dụng con trỏ một quá khứ-cuối):

Nói chung, bạn thậm chí không được phép tạo một con trỏ nằm ngoài giới hạn. Một con trỏ phải trỏ đến một phần tử trong mảng hoặc một phần tử ở phía sau phần cuối . Không nơi nào khác.

Con trỏ thậm chí không được phép tồn tại, có nghĩa là bạn rõ ràng cũng không được phép tham khảo nó.

Đây là những gì tiêu chuẩn phải nói về chủ đề này:

5,7: 5:

Khi một biểu thức có kiểu tích phâ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 đối tượng mảng và mảng đủ lớn, thì 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 phần tử mảng kết quả và ban đầu bằng biểu thức tích phân. Nói cách khác, nếu biểu thức P trỏ đến phần tử thứ i của một đối tượng mảng thì các biểu thức (P) + N (tương đương, N + (P)) và (P) -N (trong đó N có giá trị n) trỏ tương ứng với các phần tử thứ i + n và i-n-thứ của đối tượng mảng, miễn là chúng tồn tại. Hơn nữa, nếu biểu thức P trỏ đến phần tử cuối cùng của một đối tượng mảng, biểu thức (P) +1 trỏ một điểm qua phần tử cuối cùng của đối tượng mảng và nếu biểu thức Q trỏ một điểm qua phần tử cuối cùng của đối tượng mảng, thì biểu thức (Q) -1 trỏ đến phần tử cuối cùng của đối tượng mảng . Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng, hoặc một phần tử vượt qua phần tử cuối cùng của đối tượng mảng, thì phép đánh giá sẽ không tạo ra quá nhiều;nếu không, hành vi là hoàn tác .

(nhấn mạnh của tôi)

Tất nhiên, điều này là dành cho toán tử +. Vì vậy, chỉ để chắc chắn, đây là những gì tiêu chuẩn nói về chỉ số mảng mảng:

5.2.1: 1:

Biểu thức E1[E2]giống hệt nhau (theo định nghĩa) với*((E1)+(E2))

Tất nhiên, có một cảnh báo rõ ràng: Ví dụ của bạn không thực sự hiển thị một con trỏ nằm ngoài giới hạn. nó sử dụng một con trỏ "một quá khứ cuối", con trỏ này khác. Con trỏ được phép tồn tại (như phần trên đã nói), nhưng tiêu chuẩn, theo như tôi thấy, không nói gì về việc bỏ tham chiếu nó. Gần nhất tôi có thể tìm thấy là 3,9,2: 3:

[Lưu ý: ví dụ: địa chỉ nằm sau phần cuối của mảng (5.7) sẽ được coi là trỏ đến một đối tượng không liên quan của kiểu phần tử của mảng có thể nằm ở địa chỉ đó. —Gửi ghi chú]

Điều này đối với tôi dường như ngụ ý rằng có, bạn có thể tham khảo nó một cách hợp pháp, nhưng kết quả của việc đọc hoặc ghi vào vị trí là không xác định.

Cảm ơn ilproxyil đã sửa phần cuối cùng ở đây, trả lời phần cuối cùng của câu hỏi của bạn:

  • array + 5không thực sự bỏ qua bất cứ điều gì, nó chỉ đơn giản tạo ra một con trỏ đến một quá khứ kết thúc array.
  • &array[4] + 1dereferences array+4(hoàn toàn an toàn), lấy địa chỉ của giá trị đó và thêm một địa chỉ vào địa chỉ đó, dẫn đến một con trỏ quá khứ-cuối (nhưng con trỏ đó không bao giờ được tham chiếu đến.
  • &array[5] dereferences array + 5 (theo tôi thấy là hợp pháp và dẫn đến "một đối tượng không liên quan của kiểu phần tử của mảng", như đã nói ở trên), và sau đó lấy địa chỉ của phần tử đó, điều này cũng có vẻ đủ hợp pháp.

Vì vậy, họ không làm điều hoàn toàn giống nhau, mặc dù trong trường hợp này, kết quả cuối cùng là như nhau.


4
& array [5] đang trỏ đến một quá khứ. Tuy nhiên, đây không phải là cách hợp pháp để lấy địa chỉ đó.
Matthew Flaschen

5
câu cuối cùng không chính xác. "array + 5" và "& array [4] + 1" KHÔNG bỏ tham chiếu quá cuối, trong khi mảng [5] CÓ. (Tôi cũng cho rằng ý bạn là & mảng [5], nhưng nhận xét vẫn đứng). Hai đầu tiên chỉ đơn giản là chỉ một điểm qua điểm cuối.
user83255

2
@jalf lưu ý rằng - tôi nghĩ nó chỉ muốn nói rằng "a + sizeof a" có giá trị như nhau đối với "& b" nếu b được cấp phát trực tiếp sau mảng "a" và các địa chỉ kết quả đều "trỏ tới" giống nhau vật. Không hơn. Hãy nhớ rằng tất cả các ghi chú đều mang tính thông tin (không mang tính quy chuẩn): Nếu nó sẽ nêu những sự kiện quan trọng cơ bản như có các đối tượng sau một đối tượng mảng được đặt ở cuối quá khứ, thì quy tắc như vậy sẽ cần phải được thực hiện theo quy chuẩn
Johannes Schaub - litb

2
Theo ANSI C (C89 / C90), đây là câu trả lời chính xác. Nếu bạn tuân theo tiêu chuẩn về ký tự, & mảng [5] không hợp lệ về mặt kỹ thuật, trong khi mảng + 5 là hợp lệ, mặc dù hầu hết mọi trình biên dịch sẽ tạo ra cùng một mã cho cả hai biểu thức. C99 cập nhật tiêu chuẩn để cho phép rõ ràng & mảng [5]. Xem câu trả lời của tôi để biết đầy đủ chi tiết.
Adam Rosenfield

2
@jalf, cũng là toàn bộ văn bản có ghi chú bắt đầu bằng "Nếu một đối tượng kiểu T được đặt tại địa chỉ A ..." <- Điều đó nói rằng "Văn bản sau giả định có một đối tượng ở địa chỉ A." Vì vậy, trích dẫn của bạn không (và không thể, với điều kiện này) nói rằng luôn có một đối tượng ở địa chỉ A.
Johannes Schaub - litb

44

Vâng, nó hợp pháp. Từ tiêu chuẩn dự thảo C99 :

§6.5.2.1, đoạn 2:

Biểu thức hậu tố theo sau là một biểu thức trong dấu ngoặc vuông []là một ký hiệu được chỉ định dưới dạng ký hiệu con của một phần tử của một đối tượng mảng. Định nghĩa của toán tử [] chỉ số con E1[E2]giống với (*((E1)+(E2))). Do các quy tắc chuyển đổi áp dụng cho +toán tử nhị phân , nếu E1là một đối tượng mảng (tương đương, một con trỏ đến phần tử ban đầu của một đối tượng mảng) và E2là một số nguyên, E1[E2]chỉ định E2phần tử thứ-của E1(đếm từ 0).

§6.5.3.2, đoạn 3 (tôi nhấn mạnh):

Các unary &nhà điều hành mang lại địa chỉ của toán hạng của nó. Nếu toán hạng có kiểu '' kiểu '', kết quả có kiểu '' con trỏ cần nhập ''. Nếu toán hạng là kết quả của toán tử một ngôi *, cả toán tử đó và toán tử đều không &được đánh giá và kết quả giống như cả hai đều bị bỏ qua, ngoại trừ các ràng buộc đối với toán tử vẫn áp dụng và kết quả không phải là giá trị. Tương tự, nếu toán hạng là kết quả của một []toán tử, thì cả toán tử & và toán tử *một ngôi được ngụ ý bởi hàm []đều được đánh giá và kết quả giống như khi &toán tử bị xóa và []toán tử được thay đổi thành một +toán tử. Nếu không, kết quả là một con trỏ tới đối tượng hoặc hàm được chỉ định bởi toán hạng của nó.

§6.5.6, đoạn 8:

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, thì kết quả trỏ đến một phần tử khác với phần tử ban đầu sao cho hiệu số của các chỉ số con của phần tử mảng kết quả và ban đầu bằng biểu thức số nguyên. Nói cách khác, nếu biểu thức Ptrỏ đến iphần tử -th của một đối tượng mảng, thì các biểu thức (P)+N(tương đương, N+(P)) và (P)-N(trong đó Ncó giá trị n) lần lượt trỏ đến các phần tử -th i+ni−n-th của đối tượng mảng, miễn là chúng hiện hữu. Hơn nữa, nếu biểu thứcPtrỏ đến phần tử cuối cùng của đối tượng mảng, biểu thức (P)+1trỏ tới phần tử cuối cùng của đối tượng mảng và nếu biểu thức Qtrỏ đến phần tử cuối cùng của đối tượng mảng, thì biểu thức (Q)-1trỏ đến phần tử cuối cùng của đối tượng mảng. Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử vượt qua phần tử cuối cùng của đối tượng mảng, thì phép đánh giá sẽ không tạo ra lỗi tràn; nếu không, hành vi là không xác định. Nếu kết quả trỏ qua phần tử cuối cùng của đối tượng mảng, nó sẽ không được sử dụng làm toán hạng của toán tử một ngôi *được đánh giá.

Lưu ý rằng tiêu chuẩn cho phép con trỏ trỏ một phần tử qua phần cuối của mảng một cách rõ ràng, miễn là chúng không được tham chiếu đến . Bởi 6.5.2.1 và 6.5.3.2, biểu thức &array[5]tương đương với &*(array + 5), tương đương với (array+5), chỉ một điểm qua phần cuối của mảng. Điều này không dẫn đến một cuộc bỏ phiếu (theo 6.5.3.2), vì vậy nó là hợp pháp.


1
Anh ấy hỏi rõ ràng về C ++. Đây là loại khác biệt nhỏ mà không thể dựa vào khi chuyển đổi giữa hai loại.
Matthew Flaschen

3
Anh ấy hỏi về cả hai: "Cũng sẽ rất thú vị nếu biết nó có đáp ứng tiêu chuẩn C."
CB Bailey

2
@Matthew Flaschen: Tiêu chuẩn C ++ kết hợp tiêu chuẩn C theo tham chiếu. Phụ lục C.2 chứa danh sách các thay đổi (sự không tương thích giữa ISO C và ISO C ++) và không có thay đổi nào liên quan đến các điều khoản này. Do đó, & array [5] là hợp pháp trong C và C ++.
Adam Rosenfield

12
Tiêu chuẩn C là một tham chiếu quy chuẩn trong tiêu chuẩn C ++. Điều đó có nghĩa là các điều khoản trong tiêu chuẩn C được tham chiếu bởi tiêu chuẩn C ++ là một phần của tiêu chuẩn C ++. Nó không có nghĩa là mọi thứ trong tiêu chuẩn C đều được áp dụng. Đặc biệt, Phụ lục C mang tính thông tin, không mang tính quy chuẩn, vì vậy chỉ vì sự khác biệt không được nêu rõ trong phần này không có nghĩa là C 'phiên bản' áp dụng cho C ++.
CB Bailey

1
@Adam: Cảm ơn vì đã chỉ ra điều đó. Tôi chưa bao giờ chắc chắn về lịch sử của C. Re C ++ 0x đề cập đến C99, kiến ​​thức nhỏ của tôi về những thay đổi trong C99, tôi khá chắc chắn rằng C ++ sẽ tiếp tục đề cập đến C89 / 90 và anh đào sẽ chọn những thay đổi "mong muốn" từ C99 theo từng trường hợp . Câu hỏi / câu trả lời này là một ví dụ điển hình về điều này. Tôi muốn nói rằng C ++ sẽ tiếp tục sử dụng không có "lvalue-to-rvalue" do đó không có hành vi không xác định, thay vì tích hợp từ "& * == no-op".
Richard Corden

17

hợp pháp.

Theo tài liệu gcc cho C ++ , &array[5]là hợp pháp. Trong cả C ++ và C, bạn có thể giải quyết một cách an toàn phần tử ở cuối một mảng - bạn sẽ nhận được một con trỏ hợp lệ. Vì vậy, &array[5]như một biểu thức là hợp pháp.

Tuy nhiên, nó vẫn là hành vi không xác định để cố gắng bỏ tham chiếu con trỏ đến bộ nhớ chưa được phân bổ, ngay cả khi con trỏ trỏ đến một địa chỉ hợp lệ. Vì vậy, cố gắng bỏ tham chiếu đến con trỏ được tạo bởi biểu thức đó vẫn là hành vi không xác định (tức là bất hợp pháp) mặc dù bản thân con trỏ là hợp lệ.

Trong thực tế, tôi tưởng tượng nó thường sẽ không gây ra sự cố.

Chỉnh sửa: Nhân tiện, đây nói chung là cách trình lặp end () cho vùng chứa STL được triển khai (như một con trỏ đến một quá khứ-cuối), vì vậy đó là một minh chứng khá tốt cho việc thực hành là hợp pháp.

Chỉnh sửa: Ồ, bây giờ tôi thấy bạn không thực sự hỏi rằng việc giữ con trỏ đến địa chỉ đó có hợp pháp hay không, nhưng liệu cách lấy con trỏ chính xác đó có hợp pháp không. Tôi sẽ hoãn lại những người trả lời khác về vấn đề đó.


4
Tôi muốn nói rằng bạn đúng, nếu và chỉ khi thông số kỹ thuật C ++ không nói rằng & * phải được coi là không chọn. Tôi tưởng tượng nó có thể không nói điều đó.
Tyler McHenry

8
trang mà bạn tham chiếu (một cách chính xác) nói rằng việc trỏ một trang quá cuối là hợp pháp . & array [5], về mặt kỹ thuật trước tiên là tham chiếu (mảng + 5), sau đó tham chiếu lại. Vì vậy, về mặt kỹ thuật nó là như thế này: (& * (array + 5)). May mắn thay, trình biên dịch đủ thông minh để biết rằng & * có thể là yếu tố vô ích. Tuy nhiên, họ không phải làm điều đó, do đó, tôi muốn nói đó là UB.
Evan Teran

4
@Evan: Còn nhiều thứ nữa. Kiểm tra dòng cuối cùng của vấn đề cốt lõi 232: std.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#232 . Ví dụ cuối cùng ở đó có vẻ sai - nhưng họ giải thích rõ ràng rằng sự khác biệt nằm ở chuyển đổi "lvalue-to-rvalue", trong trường hợp này không xảy ra.
Richard Corden

@Richard: thú vị, có vẻ như có một số cuộc tranh luận về chủ đề này. Tôi thậm chí đồng ý rằng nó nên được phép :-P.
Evan Teran

2
Nó là cùng một loại hành vi undefined như là "tài liệu tham khảo-to-NULL" điều người giữ thảo luận về và nơi dường như tất cả các bình chọn lên câu trả lời nói rằng "đó là hành vi không xác định"
Johannes Schaub - litb

10

Tôi tin rằng điều này là hợp pháp và nó phụ thuộc vào việc chuyển đổi 'giá trị thành giá trị' đang diễn ra. Dòng cuối cùng Vấn đề cốt lõi 232 có như sau:

Chúng tôi đồng ý rằng cách tiếp cận trong tiêu chuẩn có vẻ ổn: p = 0; * p; vốn dĩ không phải là một lỗi. Một chuyển đổi giá trị thành giá trị sẽ cung cấp cho nó hành vi không xác định

Mặc dù đây là một ví dụ hơi khác, nhưng những gì nó cho thấy là '*' không dẫn đến chuyển đổi giá trị thành giá trị và do đó, vì biểu thức là toán hạng ngay lập tức của '&' mong đợi một giá trị sau đó hành vi được xác định.


+1 cho liên kết thú vị. Tôi vẫn không chắc rằng tôi đồng ý rằng p = 0; * p; được định nghĩa rõ ràng vì tôi không tin rằng '*' được xác định tốt cho một biểu thức có giá trị không phải là một con trỏ đến một đối tượng thực tế.
CB Bailey

Một tuyên bố đó là một biểu thức là hợp pháp và có nghĩa là để đánh giá biểu thức đó. * p là một biểu thức gọi hành vi không xác định, vì vậy bất kỳ việc triển khai nào đều tuân theo tiêu chuẩn (bao gồm gửi email cho sếp của bạn hoặc tải xuống số liệu thống kê về bóng chày).
David Thornley

1
Lưu ý rằng trạng thái của vấn đề đó vẫn đang "soạn thảo" và nó chưa trở thành tiêu chuẩn (chưa), ít nhất là những phiên bản nháp của C ++ 11 và C ++ 14 mà tôi có thể tìm thấy.
musiphil

Đề xuất "giá trị trống" chưa bao giờ được chấp nhận trong bất kỳ tiêu chuẩn đã xuất bản nào
MM

Các ghi chú trong vấn đề đề cập đến vấn đề một cách rõ ràng &*a[n]: "Tương tự, nên cho phép tham chiếu một con trỏ đến cuối một mảng miễn là giá trị không được sử dụng". Thật không may, vấn đề này đã được giải quyết từ năm 2003 và độ phân giải vẫn chưa có trong tiêu chuẩn.
Pablo Halpern

9

Tôi không tin rằng điều đó là bất hợp pháp, nhưng tôi tin rằng hành vi của & array [5] là không xác định.

  • 5.2.1 [expr.sub] E1 [E2] giống hệt (theo định nghĩa) với * ((E1) + (E2))

  • 5.3.1 [expr.unary.op] unary * operator ... kết quả là một giá trị tham chiếu đến đối tượng hoặc hàm mà biểu thức trỏ tới.

Tại thời điểm này, bạn có hành vi không xác định vì biểu thức ((E1) + (E2)) không thực sự trỏ đến một đối tượng và tiêu chuẩn cho biết kết quả phải như thế nào trừ khi nó thực hiện.

  • 1.3.12 [defns.undefined] Hành vi không xác định cũng có thể xảy ra khi tiêu chuẩn này bỏ qua phần mô tả về bất kỳ định nghĩa rõ ràng nào về hành vi.

Như đã lưu ý ở phần khác, array + 5&array[0] + 5là những cách hợp lệ và được xác định rõ ràng để lấy một con trỏ nằm ngoài phần cuối của mảng.


Điểm mấu chốt là: "kết quả của '*' là một giá trị". Từ những gì tôi có thể nói, nó chỉ trở thành UB mà bạn có một chuyển đổi giá trị thành giá trị trên kết quả đó.
Richard Corden

1
Tôi sẽ cho rằng kết quả của '*' chỉ được xác định theo đối tượng mà biểu thức mà toán tử được áp dụng, sau đó nó không được xác định - do thiếu sót - kết quả là gì nếu biểu thức không có giá trị thực sự tham chiếu đến một đối tượng. Tuy nhiên, nó còn lâu mới rõ ràng.
CB Bailey

7

Ngoài các câu trả lời trên, tôi sẽ chỉ ra toán tử & có thể được ghi đè cho các lớp. Vì vậy, ngay cả khi nó hợp lệ đối với POD, có lẽ không phải là ý kiến ​​hay khi thực hiện đối với một đối tượng mà bạn biết là không hợp lệ (giống như ghi đè toán tử & () ngay từ đầu).


4
+1 khi đưa toán tử & vào cuộc thảo luận, ngay cả khi các chuyên gia khuyên không bao giờ ghi đè nó vì một số vùng chứa STL phụ thuộc vào nó trả về một con trỏ vào phần tử. Đó là một trong những thứ đã đi vào tiêu chuẩn trước khi họ biết rõ hơn.
David Rodríguez - dribeas

4

Điều này là hợp pháp:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Phần 5.2.1 Ký hiệu Biểu thức E1 [E2] giống hệt nhau (theo định nghĩa) với * ((E1) + (E2))

Vì vậy, chúng ta có thể nói rằng array_end cũng tương đương:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))

Phần 5.3.1.1 Toán tử một ngôi '*': Toán tử một ngôi * thực hiện điều hướng: biểu thức mà nó được áp dụng sẽ là một con trỏ đến một kiểu đối tượng hoặc một con trỏ đến một kiểu hàm và kết quả là một giá trị tham chiếu đến đối tượng hoặc hàm mà biểu thức trỏ đến. Nếu kiểu của biểu thức là "con trỏ tới T", thì loại kết quả là "T." [Lưu ý: một con trỏ đến một kiểu không hoàn chỉnh (không phải cv void) có thể được tham chiếu đến. Giá trị thu được do đó có thể được sử dụng theo những cách hạn chế (để khởi tạo một tham chiếu chẳng hạn); giá trị này không được chuyển đổi thành giá trị, xem 4.1. - ghi chú cuối]

Phần quan trọng của những điều trên:

'kết quả là một giá trị tham chiếu đến đối tượng hoặc chức năng'.

Toán tử một ngôi '*' đang trả về một giá trị tham chiếu đến int (không có tham chiếu). Toán tử một ngôi '&' sau đó lấy địa chỉ của giá trị.

Miễn là không có sự hủy tham chiếu của một con trỏ ngoài giới hạn thì hoạt động được bao phủ hoàn toàn bởi tiêu chuẩn và tất cả hành vi được xác định. Vì vậy, bằng cách đọc của tôi ở trên là hoàn toàn hợp pháp.

Thực tế là rất nhiều thuật toán STL phụ thuộc vào hành vi được xác định rõ ràng, là một loại gợi ý rằng ủy ban tiêu chuẩn đã mặc dù vậy và tôi chắc chắn rằng có một cái gì đó bao gồm điều này một cách rõ ràng.

Phần bình luận dưới đây đưa ra hai lập luận:

(vui lòng đọc: nhưng nó dài và cả hai chúng tôi kết thúc trollish)

Đối số 1

điều này là bất hợp pháp vì mục 5.7 đoạn 5

Khi một biểu thức có kiểu tích phâ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, thì kết quả trỏ đến một phần tử khác với phần tử ban đầu sao cho hiệu số của các chỉ số con của phần tử mảng kết quả và ban đầu bằng biểu thức tích phân. Nói cách khác, nếu biểu thức P trỏ đến phần tử thứ i của một đối tượng mảng thì các biểu thức (P) + N (tương đương, N + (P)) và (P) -N (trong đó N có giá trị n) trỏ tương ứng với các phần tử thứ i + n và i - n của đối tượng mảng, miễn là chúng tồn tại. Hơn nữa, nếu biểu thức P trỏ đến phần tử cuối cùng của một đối tượng mảng, thì biểu thức (P) +1 trỏ một điểm qua phần tử cuối cùng của đối tượng mảng, và nếu biểu thức Q trỏ qua phần tử cuối cùng của đối tượng mảng thì biểu thức (Q) -1 trỏ đến phần tử cuối cùng của đối tượng mảng. Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử vượt qua phần tử cuối cùng của đối tượng mảng, thì phép đánh giá sẽ không tạo ra lỗi tràn; nếu không, hành vi là không xác định.

Và mặc dù phần này có liên quan; nó không hiển thị hành vi không xác định. Tất cả các phần tử trong mảng mà chúng ta đang nói đến đều nằm trong mảng hoặc nằm sau phần cuối của mảng (được xác định rõ bởi đoạn trên).

Đối số 2:

Đối số thứ hai được trình bày dưới đây là: *là toán tử khử tham chiếu.
Và mặc dù đây là một thuật ngữ phổ biến được sử dụng để mô tả toán tử '*'; thuật ngữ này được cố ý tránh trong tiêu chuẩn vì thuật ngữ 'hủy tham chiếu' không được xác định rõ về ngôn ngữ và ý nghĩa của điều đó đối với phần cứng cơ bản.

Mặc dù việc truy cập bộ nhớ nằm ngoài phần cuối của mảng chắc chắn là hành vi không xác định. Tôi không tin rằng việc unary * operatortruy cập bộ nhớ (đọc / ghi vào bộ nhớ) trong ngữ cảnh này (không theo cách mà tiêu chuẩn xác định). Trong ngữ cảnh này (như được định nghĩa bởi tiêu chuẩn (xem 5.3.1.1))) unary * operatortrả về a lvalue referring to the object. Theo hiểu biết của tôi về ngôn ngữ, đây không phải là quyền truy cập vào bộ nhớ cơ bản. Kết quả của biểu thức này sau đó được sử dụng ngay lập tức bởi unary & operatortoán tử trả về địa chỉ của đối tượng được tham chiếu bởi lvalue referring to the object.

Nhiều tài liệu tham khảo khác đến Wikipedia và các nguồn không chính tắc được trình bày. Tất cả những điều đó tôi thấy không liên quan. C ++ được định nghĩa theo tiêu chuẩn .

Phần kết luận:

Tôi muốn thừa nhận rằng có nhiều phần của tiêu chuẩn mà tôi có thể đã không xem xét và có thể chứng minh những lập luận trên của tôi là sai. KHÔNG được cung cấp bên dưới. Nếu bạn cho tôi xem một tham chiếu tiêu chuẩn cho thấy đây là UB. tôi sẽ

  1. Để lại câu trả lời.
  2. Nói chung là điều này thật ngu ngốc và tôi đã sai cho tất cả mọi người khi đọc.

Đây không phải là một đối số:

Không phải mọi thứ trên toàn thế giới đều được định nghĩa bởi tiêu chuẩn C ++. Mở rộng tâm trí của bạn.


2
Theo ai? Theo đoạn văn nào? Thực *hiện một cuộc hội thảo. Nó là toán tử tham khảo. Đây là những gì nó làm. Có thể cho rằng việc bạn nhận được một con trỏ mới đến giá trị kết quả (sử dụng &) là không liên quan. Bạn không thể chỉ trình bày một chuỗi đánh giá, trình bày ngữ nghĩa biểu thức cuối cùng và giả vờ rằng các bước trung gian không xảy ra (hoặc các quy tắc của ngôn ngữ không áp dụng cho từng bước).
Các cuộc đua ánh sáng trong quỹ đạo

3
Tôi trích dẫn từ cùng một đoạn văn: the result is an lvalue referring to the object or function to which the expression points.Rõ ràng là nếu không có đối tượng như vậy tồn tại, thì không có hành vi nào được định nghĩa cho toán tử này. Tuyên bố sau đó của bạn is returning a lvalue referring to the int (no de-refeference)là điều không có ý nghĩa đối với tôi. Tại sao bạn nghĩ rằng đây không phải là một cuộc hội thảo?
Các cuộc đua ánh sáng ở Orbit

2
It returns a reference to what is being pointed at.Đây là gì, nếu không phải là một cuộc hội thảo? Đoạn văn nói rằng *thực hiện chuyển hướng và chuyển hướng từ con trỏ đến con trỏ được gọi là hội nghị truyền thống. Lập luận của bạn về cơ bản khẳng định rằng con trỏ và tham chiếu là cùng một thứ hoặc, ít nhất, được liên kết ngầm, điều này đơn giản là không đúng. int x = 0; int* ptr = &x; int& y = *x;Đây, tôi xin mời x. Tôi không cần phải sử dụng ycho điều đó là đúng.
Các cuộc đua ánh sáng trong quỹ đạo

2
@LokiAstari: Tôi có một câu hỏi, bạn nghĩ "dereferencing" có nghĩa là gì nếu không phải "gọi toán *tử một ngôi trả về giá trị tham chiếu đến đối tượng mà biểu thức trỏ đến"? (Lưu ý rằng các câu tiếp theo của tiêu chuẩn không đề cập đến quá trình này như dereferencing trong C ++ 11 spec)
Mooing Duck

2
@LokiAstari: Tôi đã cho bạn xem tài liệu tham khảo vài ngày trước ; bạn chỉ đơn giản là từ chối thừa nhận rằng chúng tồn tại, vì một số lý do. Làm thế nào bạn có thể biện minh cho hành vi này là ngoài tôi, nhưng bạn phải là người trolling.
Lightness Races ở Orbit

2

Bản nháp đang làm việc ( n2798 ):

"Kết quả của toán tử & một ngôi là một con trỏ đến toán hạng của nó. Toán hạng sẽ là giá trị hoặc id định tính. Trong trường hợp đầu tiên, nếu kiểu của biểu thức là" T ", thì loại kết quả là" con trỏ tới T. ”" (trang 103)

array [5] không phải là id đủ điều kiện tốt nhất mà tôi có thể nói (danh sách ở trang 87); gần nhất dường như là số nhận dạng, nhưng trong khi mảng là số nhận dạng, mảng [5] thì không. Nó không phải là một lvalue vì "Một lvalue đề cập đến một đối tượng hoặc chức năng." (Tr. 76). array [5] rõ ràng không phải là một hàm và không được đảm bảo tham chiếu đến một đối tượng hợp lệ (vì mảng + 5 nằm sau phần tử mảng được cấp phát cuối cùng).

Rõ ràng, nó có thể hoạt động trong một số trường hợp nhất định, nhưng nó không phải là C ++ hợp lệ hoặc an toàn.

Lưu ý: Việc thêm để lấy một mảng vượt qua là hợp pháp (tr. 113):

"nếu biểu thức P [a pointer] trỏ đến phần tử cuối cùng của đối tượng mảng, thì biểu thức (P) +1 trỏ một điểm qua phần tử cuối cùng của đối tượng mảng và nếu biểu thức Q trỏ một điểm qua phần tử cuối cùng của một đối tượng mảng, biểu thức (Q) -1 trỏ đến phần tử cuối cùng của đối tượng mảng. Nếu cả toán hạng con trỏ và kết quả đều trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử sau cùng của đối tượng mảng, đánh giá sẽ không tạo ra quá nhiều "

Nhưng nó không hợp pháp để làm như vậy bằng cách sử dụng &.


1
Tôi ủng hộ bạn, bởi vì bạn đúng. Không có đối tượng nào được đảm bảo nằm ở vị trí quá khứ-cuối. Người phản đối bạn có thể đã hiểu lầm bạn (bạn có vẻ như bạn nói bất kỳ mảng-index-op nào đề cập đến không có đối tượng nào cả). Tôi nghĩ đây là một điều thú vị: Nó một lvalue, nhưng nó cũng không đề cập đến một đối tượng. Và do đó, đây là một mâu thuẫn với những gì tiêu chuẩn nói. Và do đó, điều này dẫn đến hành vi không xác định :) Điều này cũng liên quan đến hành vi này: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232
Johannes Schaub - litb

2
@jalf, ghi chú cho biết "có thể nằm ở địa chỉ đó". Nó không được đảm bảo rằng có một trong những vị trí :)
Johannes Schaub - litb

1
Tiêu chuẩn nói rằng kết quả của op * phải là một giá trị, nhưng nó chỉ cho biết giá trị đó là gì nếu toán hạng là một con trỏ thực sự trỏ đến một đối tượng. Điều đó sẽ ngụ ý (kỳ lạ) rằng nếu một quá khứ không tình cờ chỉ vào một đối tượng phù hợp, thì việc triển khai sẽ phải tìm một giá trị phù hợp từ một nơi khác và sử dụng nó. Điều đó thực sự sẽ làm rối tung & array [sizeof array]!
CB Bailey

3
"Tuy nhiên, tôi vẫn nghĩ rằng nó không phải là một lvalue. Bởi vì không có đối tượng nào được đảm bảo nằm ở mảng [5], mảng [5] không thể hợp pháp / tham chiếu / đến một đối tượng." <- Đó chính là lý do tại sao tôi nghĩ rằng đó là hành vi không xác định: Nó phụ thuộc vào một số hành vi không được xác định một cách rõ ràng theo tiêu chuẩn, và do đó nằm trong 1.3.12 [defns.undefined]
Johannes Schaub - litb

1
litb, đủ công bằng. Giả sử nó / không chắc chắn / một giá trị, và do đó / chắc chắn không / 100% an toàn.
Matthew Flaschen

2

Ngay cả khi nó là hợp pháp, tại sao lại rời khỏi quy ước? mảng + 5 dù sao cũng ngắn hơn và theo tôi, dễ đọc hơn.

Chỉnh sửa: Nếu bạn muốn nó đối xứng, bạn có thể viết

int* array_begin = array; 
int* array_end = array + 5;

Tôi nghĩ rằng kiểu mà tôi sử dụng trong câu hỏi trông cân xứng hơn: khai báo mảng và con trỏ bắt đầu / kết thúc hoặc đôi khi tôi truyền trực tiếp chúng vào một hàm STL. Đó là lý do tại sao tôi sử dụng nó thay vì phiên bản ngắn hơn.
Zan Lynx

Để đối xứng, tôi nghĩ nó cần phải là array_begin = array + 0; array_end = array + 5; Làm thế nào đó là một phản hồi bình luận bị trì hoãn lâu?
Zan Lynx

Nó có thể là một kỷ lục thế giới :)
rlbond

1

Nó phải là hành vi không xác định, vì những lý do sau:

  1. Việc cố gắng truy cập các phần tử nằm ngoài giới hạn dẫn đến hành vi không xác định. Do đó, tiêu chuẩn không cấm việc triển khai đưa ra một ngoại lệ trong trường hợp đó (tức là giới hạn kiểm tra việc triển khai trước khi một phần tử được truy cập). Nếu & (array[size])được định nghĩa là như vậy begin (array) + size, một triển khai đưa ra một ngoại lệ trong trường hợp truy cập ngoài giới hạn sẽ không phù hợp với tiêu chuẩn nữa.

  2. Không thể tạo ra lợi nhuận này end (array)nếu mảng không phải là một mảng mà là một kiểu tập hợp tùy ý.


0

Tiêu chuẩn C ++, 5.19, đoạn 4:

Một biểu thức hằng địa chỉ là một con trỏ đến một giá trị ... Con trỏ phải được tạo một cách rõ ràng, sử dụng toán tử đơn nguyên & ... hoặc sử dụng biểu thức của kiểu mảng (4.2) .... Toán tử chỉ số con [] ... có thể được sử dụng để tạo biểu thức hằng địa chỉ, nhưng giá trị của một đối tượng sẽ không được truy cập bằng cách sử dụng các toán tử này. Nếu toán tử chỉ số con được sử dụng, một trong các toán hạng của nó phải là một biểu thức hằng số tích phân.

Đối với tôi thì có vẻ như & array [5] là C ++ hợp pháp, là một biểu thức hằng địa chỉ.


1
Tôi không chắc rằng câu hỏi ban đầu có nhất thiết phải nói về một mảng có lưu trữ tĩnh. Ngay cả khi tôi tự hỏi liệu & array [5] không phải là một biểu thức hằng địa chỉ chính xác vì nó không trỏ đến một giá trị chỉ định một đối tượng?
CB Bailey

Tôi không nghĩ mảng là tĩnh hay được cấp phát ngăn xếp.
David Thornley

Nó thực hiện nếu bạn tham khảo 5.19. Phần mà bạn đã giải thích ... cho biết "... chỉ định một đối tượng có thời lượng lưu trữ tĩnh, một chuỗi ký tự hoặc một hàm. ...". Điều này có nghĩa là nếu biểu thức của bạn liên quan đến một mảng được phân bổ ngăn xếp, bạn không thể sử dụng 5.19 để giải thích về tính hợp lệ của các biểu thức đó.
CB Bailey

Trích dẫn của bạn nói rằng nếu &array[5]là hợp pháp và được tham chiếu đến một mảng thời lượng lưu trữ tĩnh, thì đó sẽ là một hằng số địa chỉ. (So ​​sánh với &array[99]ví dụ, không có văn bản nào trong đoạn này phân biệt giữa hai trường hợp đó).
MM

-1

Nếu ví dụ của bạn KHÔNG phải là một trường hợp chung mà là một trường hợp cụ thể, thì nó được phép. Bạn có thể hợp pháp , AFAIK, di chuyển một phần qua khối bộ nhớ được cấp phát. Nó không hoạt động đối với trường hợp chung chung, tức là khi bạn đang cố gắng truy cập các phần tử xa hơn 1 từ phần cuối của mảng.

Vừa tìm kiếm C-Faq: văn bản liên kết


câu trả lời hàng đầu cho biết "nó hợp pháp" và tôi cũng nói điều tương tự. Tại sao lại down vote :). Có gì đó sai với câu trả lời của tôi?
Aditya Sehgal

-2

Nó hoàn toàn hợp pháp.

Lớp mẫu vectơ <> từ stl thực hiện chính xác điều này khi bạn gọi myVec.end (): nó lấy cho bạn một con trỏ (ở đây là một trình lặp) trỏ một phần tử qua phần cuối của mảng.


Nhưng nó làm như vậy thông qua số học con trỏ, không phải bằng cách tạo một tham chiếu đến quá khứ-cuối và sau đó áp dụng toán tử address-of cho giá trị đó.
Ben Voigt
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.