Là tăng một con trỏ đến một mảng động có kích thước 0 không xác định?


34

AFAIK, mặc dù chúng tôi không thể tạo mảng bộ nhớ tĩnh có kích thước 0, nhưng chúng tôi có thể làm điều đó với các mảng động:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

Như tôi đã đọc, phoạt động như yếu tố một quá khứ. Tôi có thể in địa chỉ đó p.

if(p)
    cout << p << endl;
  • Mặc dù tôi chắc chắn rằng chúng ta không thể hủy bỏ con trỏ đó (yếu tố quá khứ cuối cùng) như chúng ta không thể với các trình vòng lặp (yếu tố quá khứ cuối cùng), nhưng điều tôi không chắc chắn là liệu có tăng con trỏ đó pkhông? Là một hành vi không xác định (UB) như với các trình vòng lặp?

    p++; // UB?

4
UB "... Bất kỳ tình huống nào khác (nghĩa là cố gắng tạo một con trỏ không trỏ đến một phần tử của cùng một mảng hoặc một quá khứ) sẽ gọi hành vi không xác định ...." từ: en.cppreference.com / w / cpp / ngôn ngữ / toán
tử_arith chữ số

3
Vâng, điều này tương tự như với một std::vectormục 0 trong đó. begin()đã bằng với end()vì vậy bạn không thể tăng một trình vòng lặp đang trỏ vào đầu.
Phil1970

1
@PeterMortensen Tôi nghĩ rằng chỉnh sửa của bạn đã thay đổi ý nghĩa của câu cuối cùng ("Điều tôi chắc chắn về -> Tôi không chắc tại sao"), bạn có thể vui lòng kiểm tra lại không?
Fabio nói Phục hồi lại

@PeterMortensen: Đoạn cuối bạn đã chỉnh sửa đã trở nên ít đọc hơn.
Itachi Uchiwa

Câu trả lời:


32

Con trỏ tới các phần tử của mảng được phép trỏ đến một phần tử hợp lệ hoặc một phần tử ở cuối. Nếu bạn tăng một con trỏ theo cách vượt quá một kết thúc, hành vi không được xác định.

Đối với mảng có kích thước 0 của bạn, pđã chỉ một điểm qua cuối, vì vậy việc tăng nó không được phép.

Xem C ++ 17 8.7 / 4 về +toán tử ( ++có cùng các hạn chế):

f biểu thức Ptrỏ đến phần tử x[i]của một đối tượng mảng xcó n phần tử, các biểu thức P + JJ + P(trong đó Jcó giá trị j) trỏ đến phần tử (có thể là giả thuyết) x[i+j]nếu 0≤i + j≤n; mặt khác, hành vi là không xác định.


2
Vậy trường hợp duy nhất x[i]giống như x[i + j]khi cả hai ijcó giá trị 0?
Yên Rami

8
@RamiYen x[i]là yếu tố giống như x[i+j]nếu j==0.
liên

1
Ugh, tôi ghét "vùng hoàng hôn" của ngữ nghĩa C ++ ... +1.
einpoklum

4
@ einpoklum-rebstateMonica: Thực sự không có khu vực hoàng hôn. Đó chỉ là C ++ phù hợp ngay cả đối với trường hợp N = 0. Đối với một mảng gồm các phần tử N, có các giá trị con trỏ hợp lệ N + 1 vì bạn có thể trỏ phía sau mảng. Điều đó có nghĩa là bạn có thể bắt đầu ở đầu mảng và tăng con trỏ N lần để đi đến cuối.
MSalters

1
@MaximEgorushkin Câu trả lời của tôi là về những gì ngôn ngữ hiện đang cho phép. Thảo luận về bạn muốn nó cho phép thay vì lạc đề.
interjay

2

Tôi đoán bạn đã có câu trả lời; Nếu bạn nhìn sâu hơn một chút: Bạn đã nói rằng việc tăng một trình vòng lặp cuối cùng là UB, do đó: Câu trả lời này nằm trong trình lặp là gì?

Trình lặp chỉ là một đối tượng có một con trỏ và tăng dần mà trình lặp đó thực sự làm tăng con trỏ mà nó có. Do đó, trong nhiều khía cạnh, một trình vòng lặp được xử lý theo con trỏ.

int mảng [] = {0,1,2,3,4,5,6,7,8,9};

int * p = mảng; // p trỏ đến phần tử đầu tiên trong mảng

++ p; // p trỏ tới mảng [1]

Giống như chúng ta có thể sử dụng các trình vòng lặp để duyệt qua các phần tử trong một vectơ, chúng ta có thể sử dụng các con trỏ để duyệt qua các phần tử trong một mảng. Tất nhiên, để làm như vậy, chúng ta cần phải có được con trỏ đến phần tử đầu tiên và một phần tử qua phần tử cuối cùng. Như chúng ta vừa thấy, chúng ta có thể có được một con trỏ tới phần tử đầu tiên bằng cách sử dụng chính mảng đó hoặc bằng cách lấy địa chỉ - của phần tử đầu tiên. Chúng ta có thể có được một con trỏ cuối cùng bằng cách sử dụng một thuộc tính đặc biệt khác của mảng. Chúng ta có thể lấy địa chỉ của phần tử không tồn tại qua phần tử cuối cùng của một mảng:

int * e = & Array [10]; // con trỏ vừa qua phần tử cuối cùng trong mảng

Ở đây, chúng tôi đã sử dụng toán tử đăng ký để lập chỉ mục một phần tử chưa tồn tại; mảng có mười phần tử, vì vậy phần tử cuối cùng trong mảng nằm ở vị trí chỉ mục 9. Điều duy nhất chúng ta có thể làm với phần tử này là lấy địa chỉ của nó, chúng ta làm gì để khởi tạo e. Giống như một trình vòng lặp đầu cuối (§ 3.4.1, trang 106), một con trỏ tắt không trỏ đến một phần tử. Do đó, chúng tôi có thể không tham gia hoặc tăng con trỏ ngoài luồng.

Đây là từ phiên bản C ++ primer 5 của Lipmann.

Vì vậy, đó là UB không làm điều đó.


-4

Theo nghĩa chặt chẽ nhất, đây không phải là Hành vi không xác định, mà là xác định do triển khai. Vì vậy, mặc dù không thích hợp nếu bạn có kế hoạch hỗ trợ các kiến ​​trúc không chính thống, bạn có thể thể làm điều đó.

Trích dẫn tiêu chuẩn được đưa ra bởi interjay là một câu nói hay, chỉ ra UB, nhưng nó chỉ là tác phẩm hay thứ hai theo ý kiến ​​của tôi, vì nó liên quan đến số học con trỏ con trỏ (một cách rõ ràng, một cái rõ ràng là UB, trong khi cái kia thì không). Có một đoạn liên quan đến hoạt động trong câu hỏi trực tiếp:

[expr.post.incr] / [expr.pre.incr]
Toán hạng sẽ là [...] hoặc một con trỏ tới loại đối tượng được xác định hoàn toàn.

Oh, đợi một lát, một loại đối tượng hoàn toàn xác định? Đó là tất cả? Ý tôi là, thực sự, loại ? Vì vậy, bạn không cần một đối tượng ở tất cả?
Phải mất khá nhiều thời gian để đọc thực sự tìm thấy một gợi ý rằng một cái gì đó trong đó có thể không được xác định rõ ràng. Bởi vì cho đến nay, nó đọc như thể bạn hoàn toàn được phép làm điều đó, không có hạn chế.

[basic.compound] 3đưa ra tuyên bố về loại con trỏ mà người ta có thể có, và không phải là một trong ba loại kia, kết quả của hoạt động của bạn rõ ràng sẽ nằm dưới 3,4: con trỏ không hợp lệ .
Tuy nhiên, điều đó không nói rằng bạn không được phép có một con trỏ không hợp lệ. Ngược lại, nó liệt kê một số điều kiện rất phổ biến, thông thường (ví dụ: kết thúc thời gian lưu trữ) khi con trỏ thường xuyên trở nên không hợp lệ. Vì vậy, đó rõ ràng là một điều cho phép xảy ra. Và thực sự:

[basic.stc] 4 Chỉ định
thông qua một giá trị con trỏ không hợp lệ và chuyển một giá trị con trỏ không hợp lệ cho hàm thỏa thuận có hành vi không xác định. Bất kỳ việc sử dụng nào khác của giá trị con trỏ không hợp lệ đều có hành vi được xác định theo thực hiện.

Chúng tôi đang thực hiện "bất kỳ cái gì khác" ở đó, vì vậy nó không phải là Hành vi không xác định, nhưng được xác định theo triển khai, do đó thường được phép (trừ khi việc triển khai nói rõ ràng điều gì đó khác biệt).

Thật không may, đó không phải là kết thúc của câu chuyện. Mặc dù kết quả cuối cùng không thay đổi gì nữa kể từ đây, nhưng điều này càng gây nhầm lẫn, bạn càng tìm kiếm "con trỏ" lâu hơn:

[basic.compound]
Giá trị hợp lệ của loại con trỏ đối tượng biểu thị địa chỉ của một byte trong bộ nhớ hoặc con trỏ null. Nếu một đối tượng thuộc loại T được đặt tại một địa chỉ A [...] được cho là trỏ đến đối tượng đó, bất kể giá trị thu được như thế nào .
[Lưu ý: Chẳng hạn, địa chỉ nằm ở cuối một mảng sẽ được coi là trỏ đến một đối tượng không liên quan đến loại phần tử của mảng có thể nằm ở địa chỉ đó. [...]].

Đọc như: OK, ai quan tâm! Miễn là một con trỏ chỉ vào đâu đó trong bộ nhớ , tôi có tốt không?

[basic.stc.dynamic.safe] Giá trị con trỏ là con trỏ có nguồn gốc an toàn [blah blah]

Đọc như: OK, có nguồn gốc an toàn, bất cứ điều gì. Nó không giải thích điều này là gì, cũng không nói tôi thực sự cần nó. An toàn có nguồn gốc-the-heck. Rõ ràng tôi vẫn có thể có con trỏ không có nguồn gốc an toàn chỉ tốt. Tôi đoán rằng việc thảo luận lại có lẽ chúng không phải là một ý tưởng hay, nhưng nó hoàn toàn được phép có chúng. Nó không nói khác.

Việc triển khai có thể có an toàn con trỏ thoải mái, trong trường hợp đó tính hợp lệ của giá trị con trỏ không phụ thuộc vào việc nó có phải là giá trị con trỏ có nguồn gốc an toàn hay không.

Ồ, vậy nó có thể không quan trọng, chỉ là những gì tôi nghĩ. Nhưng chờ đã ... "có thể không"? Điều đó có nghĩa là, nó có thể là tốt . Làm sao tôi biết?

Ngoài ra, việc triển khai có thể có an toàn con trỏ nghiêm ngặt, trong trường hợp đó, giá trị con trỏ không phải là giá trị con trỏ xuất phát an toàn là giá trị con trỏ không hợp lệ trừ khi đối tượng hoàn chỉnh được tham chiếu có thời lượng lưu trữ động và đã được khai báo trước đó

Đợi đã, vậy thậm chí có khả năng tôi cần phải gọi declare_reachable()trên mọi con trỏ? Làm sao tôi biết?

Bây giờ, bạn có thể chuyển đổi sang intptr_t, được xác định rõ, đưa ra biểu diễn số nguyên của một con trỏ có nguồn gốc an toàn. Tất nhiên, là một số nguyên, nó hoàn toàn hợp pháp và được xác định rõ để tăng nó theo ý muốn.
Và có, bạn có thể chuyển đổi intptr_ttrở lại thành một con trỏ, cũng được xác định rõ. Chỉ là, không phải là giá trị ban đầu, nó không còn được đảm bảo rằng bạn có một con trỏ có nguồn gốc an toàn (rõ ràng). Tuy nhiên, tất cả trong tất cả, đối với thư của tiêu chuẩn, trong khi được xác định theo triển khai, đây là điều hợp pháp 100% phải làm:

[expr.reinterpret.cast] 5
Giá trị của kiểu tích phân hoặc kiểu liệt kê có thể được chuyển đổi rõ ràng thành một con trỏ. Một con trỏ được chuyển đổi thành một số nguyên có kích thước đủ [...] và trở về cùng một loại con trỏ [...] giá trị ban đầu; ánh xạ giữa con trỏ và số nguyên được xác định theo cách khác.

Cuộc đuổi bắt

Con trỏ chỉ là số nguyên thông thường, chỉ có bạn tình cờ sử dụng chúng làm con trỏ. Oh nếu chỉ có vậy là đúng!
Thật không may, tồn tại các kiến ​​trúc trong đó hoàn toàn không đúng và chỉ tạo ra một con trỏ không hợp lệ (không phải là bỏ qua nó, chỉ cần có nó trong một thanh ghi con trỏ) sẽ gây ra một cái bẫy.

Vì vậy, đó là cơ sở của "thực hiện được xác định". Điều đó, và thực tế là việc tăng một con trỏ bất cứ khi nào bạn muốn, như bạn vui lòng dĩ nhiên có thể gây ra tràn, điều mà tiêu chuẩn không muốn đối phó. Sự kết thúc của không gian địa chỉ ứng dụng có thể không trùng với vị trí tràn và bạn thậm chí không biết liệu có bất kỳ thứ gì như tràn cho con trỏ trên một kiến ​​trúc cụ thể không. Nói chung, đó là một mớ hỗn độn ác mộng không liên quan đến lợi ích có thể có.

Đối phó với điều kiện một đối tượng quá khứ ở phía bên kia, rất dễ dàng: Việc thực hiện phải đơn giản là đảm bảo không có đối tượng nào được phân bổ để byte cuối cùng trong không gian địa chỉ bị chiếm dụng. Vì vậy, điều đó được xác định rõ là nó hữu ích và tầm thường để đảm bảo.


1
Logic của bạn là thiếu sót. "Vì vậy, bạn không cần một đối tượng ở tất cả?" giải thích sai Tiêu chuẩn bằng cách tập trung vào một quy tắc duy nhất. Quy tắc đó là về thời gian biên dịch, cho dù chương trình của bạn được hình thành tốt. Có một quy tắc khác về thời gian chạy. Chỉ trong thời gian chạy, bạn thực sự có thể nói về sự tồn tại của các đối tượng tại một địa chỉ nhất định. chương trình của bạn cần đáp ứng tất cả các quy tắc; quy tắc thời gian biên dịch tại thời gian biên dịch và quy tắc thời gian chạy tại thời gian chạy.
MSalters

5
Bạn có lỗi logic tương tự với "OK, ai quan tâm! Miễn là một con trỏ chỉ vào đâu đó trong bộ nhớ, tôi ổn chứ?". Không. Bạn phải tuân theo tất cả các quy tắc. Ngôn ngữ khó hiểu về "kết thúc một mảng bắt đầu từ một mảng khác" chỉ cho phép thực thi để phân bổ bộ nhớ liên tục; nó không cần phải giữ không gian trống giữa các phân bổ. Điều đó có nghĩa là mã của bạn có thể có cùng giá trị A cả khi kết thúc một đối tượng mảng và bắt đầu một đối tượng khác.
MSalters

1
"Một cái bẫy" không phải là một cái gì đó có thể được mô tả bằng hành vi "xác định thực hiện". Lưu ý rằng interjay đã tìm thấy hạn chế đối với +toán tử (từ đó ++chảy) có nghĩa là việc chỉ sau "một sau khi kết thúc" là không xác định.
Martin Bonner hỗ trợ Monica

1
@PeterCordes: Vui lòng đọc basic.stc, đoạn 4 . Nó nói "Hành vi không xác định [...] không xác định. Bất kỳ việc sử dụng nào khác của giá trị con trỏ không hợp lệ đều có hành vi được xác định theo thực hiện " . Tôi không nhầm lẫn mọi người bằng cách sử dụng thuật ngữ đó cho một ý nghĩa khác. Đây là từ ngữ chính xác. Đó không phải là hành vi không xác định.
Damon

2
Bạn hầu như không thể tìm thấy kẽ hở cho việc tăng sau nhưng bạn không trích dẫn phần đầy đủ về việc tăng sau làm gì. Tôi sẽ không nhìn vào đó ngay bây giờ. Đồng ý rằng nếu có, nó là ngoài ý muốn. Dù sao, cũng tốt như vậy nếu ISO C ++ định nghĩa nhiều thứ hơn cho các mô hình bộ nhớ phẳng, @MaximEgorushkin, có những lý do khác (như bao quanh con trỏ) vì không cho phép các công cụ tùy ý. Xem bình luận về So sánh con trỏ nên được ký hoặc không dấu trong 64-bit x86?
Peter Cordes
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.