Nó có được đảm bảo an toàn để thực hiện memcpy (0,0,0) không?


80

Tôi không thông thạo lắm về tiêu chuẩn C, vì vậy hãy chịu khó với tôi.

Tôi muốn biết nếu nó được đảm bảo, theo tiêu chuẩn, đó memcpy(0,0,0)là an toàn.

Hạn chế duy nhất mà tôi có thể tìm thấy là nếu các vùng bộ nhớ chồng lên nhau, thì hành vi không được xác định ...

Nhưng chúng ta có thể coi rằng các vùng bộ nhớ chồng lên nhau ở đây không?


7
Về mặt toán học, giao của hai tập hợp rỗng là rỗng.
Benoit

Tôi muốn kiểm tra bạn muốn (x) libc làm cho bạn, nhưng khi nó asm (elibc / glibc đây), đó là một chút quá phức tạp cho một buổi sáng sớm :)
Kevin

16
+1 Tôi thích câu hỏi này vì nó là một trường hợp kỳ lạ và vì tôi nghĩ đó memcpy(0,0,0)là một trong những đoạn mã C kỳ lạ nhất mà tôi từng thấy.
templatetypedef,

2
@eq Bạn có thực sự muốn biết không hay bạn đang ngụ ý rằng không có trường hợp nào bạn muốn? Bạn đã xem xét rằng cuộc gọi thực sự có thể là, chẳng hạn memcpy(outp, inp, len),? Và điều này có thể xảy ra trong mã ở đâu outpinpđược cấp phát động và ban đầu 0? Điều này hoạt động, ví dụ, với p = realloc(p, len+n)khi nào plenđang có 0. Bản thân tôi đã sử dụng cách memcpygọi như vậy - trong khi về mặt kỹ thuật, đó là UB, tôi chưa bao giờ gặp phải một triển khai mà nó không phải là không và không bao giờ mong đợi.
Jim Balter

7
@templatetypedef memcpy(0, 0, 0)rất có thể được dùng để đại diện cho một lệnh gọi động, không tĩnh ... tức là, các giá trị tham số đó không cần phải là các ký tự.
Jim Balter,

Câu trả lời:


71

Tôi có một phiên bản nháp của tiêu chuẩn C (ISO / IEC 9899: 1999) và nó có một số điều thú vị để nói về cuộc gọi đó. Để bắt đầu, nó đề cập đến (§7.21.1 / 2) liên quan đến memcpyđó

Trong đó một đối số được khai báo là size_tn chỉ định độ dài của mảng cho một hàm, n có thể có giá trị bằng không khi gọi hàm đó. Trừ khi được nêu rõ ràng khác trong mô tả của một hàm cụ thể trong điều khoản phụ này, các đối số con trỏ trên lệnh gọi như vậy sẽ vẫn có giá trị hợp lệ, như được mô tả trong 7.1.4 . Trong một cuộc gọi như vậy, một hàm định vị một ký tự không tìm thấy sự xuất hiện nào, một hàm so sánh hai chuỗi ký tự trả về số không và một hàm sao chép ký tự sẽ sao chép các ký tự không.

Tham chiếu được chỉ ra ở đây chỉ ra điều này:

Nếu một đối số cho một hàm có giá trị không hợp lệ (chẳng hạn như một giá trị bên ngoài miền của hàm hoặc một con trỏ bên ngoài không gian địa chỉ của chương trình, hoặc một con trỏ null hoặc một con trỏ tới vùng lưu trữ không thể sửa đổi khi tham số tương ứng không đủ điều kiện const) hoặc một loại (sau khi thăng hạng) không được mong đợi bởi một hàm có số lượng đối số thay đổi, hành vi là không xác định .

Vì vậy, nó giống như theo thông số kỹ thuật C, gọi

dẫn đến hành vi không xác định, bởi vì con trỏ rỗng được coi là "giá trị không hợp lệ".

Điều đó nói rằng, tôi sẽ hoàn toàn ngạc nhiên nếu bất kỳ triển khai thực tế nào của nó memcpybị phá vỡ nếu bạn làm điều này, vì hầu hết các triển khai trực quan mà tôi có thể nghĩ đến sẽ không làm gì cả nếu bạn nói sao chép không byte.


3
Tôi có thể khẳng định rằng các phần trích dẫn từ tiêu chuẩn dự thảo là giống hệt nhau trong tài liệu cuối cùng. Sẽ không có bất kỳ rắc rối nào với một cuộc gọi như vậy, nhưng nó vẫn sẽ là hành vi không xác định mà bạn đang dựa vào. Vì vậy, câu trả lời cho "nó có đảm bảo không" là "không".
DevSolar

8
Không có triển khai nào mà bạn sẽ sử dụng trong quá trình sản xuất sẽ tạo ra bất kỳ điều gì khác hơn là không chọn cho một cuộc gọi như vậy, nhưng các triển khai làm theo cách khác được cho phép và hợp lý ... ví dụ: trình thông dịch C hoặc trình biên dịch tăng cường với tính năng kiểm tra lỗi từ chối gọi vì nó không phù hợp. Tất nhiên điều đó sẽ không hợp lý nếu Tiêu chuẩn cho phép cuộc gọi, như nó đã làm realloc(0, 0). Các trường hợp sử dụng tương tự nhau và tôi đã sử dụng cả hai (xem nhận xét của tôi dưới câu hỏi). Thật vô nghĩa và đáng tiếc khi Tiêu chuẩn tạo ra UB này.
Jim Balter

5
"Tôi sẽ hoàn toàn ngạc nhiên nếu bất kỳ triển khai thực tế nào của memcpy bị phá vỡ nếu bạn làm điều này" - Tôi đã sử dụng một cái mà sẽ; trong thực tế, nếu bạn truyền độ dài 0 với các con trỏ hợp lệ, nó thực sự đã sao chép 65536 byte. (Vòng lặp của nó giảm độ dài và sau đó được kiểm tra).
MM

14
@MattMcNabb Quá trình triển khai đó bị hỏng.
Jim Balter

3
@MattMcNabb: Có thể thêm "đúng" vào "thực tế". Tôi nghĩ rằng tất cả chúng ta đều có những kỷ niệm không mấy đẹp đẽ về những thư viện cũ, khu ổ chuột C và tôi không chắc có bao nhiêu người trong chúng ta đánh giá cao những kỷ niệm được nhắc lại đó. :)
tmyklebu

24

Nói cho vui, ghi chú phát hành cho gcc-4.9 cho biết rằng trình tối ưu hóa của nó sử dụng các quy tắc này và ví dụ: có thể xóa điều kiện trong

sau đó cho kết quả không mong đợi khi copy(0,0,0)được gọi (xem https://gcc.gnu.org/gcc-4.9/porting_to.html ).

Tôi hơi mâu thuẫn về hành vi gcc-4.9; hành vi có thể tuân thủ các tiêu chuẩn, nhưng có thể gọi memmove (0,0,0) đôi khi là một phần mở rộng hữu ích cho các tiêu chuẩn đó.


2
Hấp dẫn. Tôi hiểu không khí xung quanh của bạn nhưng đây là trung tâm của tối ưu hóa trong C: trình biên dịch giả định rằng các nhà phát triển tuân theo các quy tắc nhất định và do đó suy ra rằng một số tối ưu hóa là hợp lệ (đó là nếu các quy tắc được tuân thủ).
Matthieu M.

2
@tmyklebu: Cho trước char *p = 0; int i=something;, đánh giá biểu thức (p+i)sẽ mang lại Hành vi không xác định ngay cả khi ibằng không.
supercat

1
@tmyklebu: Có tất cả số học con trỏ (trừ phép so sánh) trên bẫy con trỏ rỗng sẽ IMHO là một điều tốt; liệu có memcpy()nên được phép thực hiện bất kỳ số học con trỏ nào trên các đối số của nó trước khi đảm bảo số khác không là một câu hỏi khác [nếu tôi đang thiết kế các tiêu chuẩn, tôi có thể sẽ chỉ định rằng nếu plà null, p+0có thể bẫy, nhưng memcpy(p,p,0)sẽ không làm gì]. Một vấn đề lớn hơn nhiều, IMHO, là tính chất mở của hầu hết các Hành vi không xác định. Mặc dù có một số thứ thực sự nên đại diện cho Hành vi không xác định (ví dụ: gọi free(p)...
supercat

1
... và sau đó thực hiện p[0]=1;) có rất nhiều thứ nên được chỉ định là mang lại kết quả không xác định (ví dụ: so sánh quan hệ giữa các con trỏ không liên quan không nên được chỉ định là nhất quán với bất kỳ so sánh nào khác, nhưng nên được chỉ định là mang lại kết quả là 0 hoặc 1), hoặc phải được chỉ định là tạo ra một hành vi hơi lỏng hơn so với việc triển khai được xác định (trình biên dịch phải được yêu cầu ghi lại tất cả các hậu quả có thể xảy ra của ví dụ như tràn số nguyên, nhưng không chỉ định hậu quả nào sẽ xảy ra trong bất kỳ trường hợp cụ thể nào).
supercat

8
một người nào đó xin vui lòng cho tôi biết, tại sao tôi không nhận được một huy hiệu stackoverflow "bắt đầu một cuộc chiến tranh ngọn lửa" :-)
user1998586

0

Bạn cũng có thể xem xét việc sử dụng này memmoveđược thấy trong Git 2.14.x (Q3 2017)

Xem cam kết 168e635 (16 tháng 7 năm 2017) và cam kết 1773664 , cam kết f331ab9 , cam kết 5783980 (15 tháng 7 năm 2017) bởi René Scharfe ( rscharfe) .
(Hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 32f9025 , ngày 11 tháng 8 năm 2017)

Nó sử dụng macro trợ giúpMOVE_ARRAY tính toán kích thước dựa trên số phần tử được chỉ định cho chúng tôi và hỗ trợ NULL con trỏ khi số đó bằng 0.
Các memmove(3)cuộc gọi thô với NULLcó thể khiến trình biên dịch (quá háo hức) tối ưu hóa NULLviệc kiểm tra sau này .

MOVE_ARRAYthêm một trình trợ giúp an toàn và thuận tiện để di chuyển các phạm vi mục nhập mảng có khả năng chồng chéo.
Nó suy ra kích thước phần tử, nhân lên một cách tự động và an toàn để lấy kích thước theo byte, thực hiện kiểm tra an toàn kiểu cơ bản bằng cách so sánh kích thước phần tử và không giống như memmove(3)nó hỗ trợ NULLcon trỏ iff 0 phần tử sẽ được di chuyển.

Ví dụ :

Nó sử dụng macroBUILD_ASSERT_OR_ZERO xác nhận phụ thuộc thời gian xây dựng, như một biểu thức (với @condlà điều kiện thời gian biên dịch phải đúng).
Quá trình biên dịch sẽ không thành công nếu điều kiện không đúng hoặc trình biên dịch không thể đánh giá.

Thí dụ:


2
Sự tồn tại của các trình tối ưu hóa cho rằng "thông minh" và "ngu ngốc" là từ trái nghĩa khiến việc kiểm tra n là cần thiết, nhưng mã hiệu quả hơn nói chung sẽ có thể thực hiện được trên một triển khai đảm bảo rằng memmove (bất kỳ, bất kỳ, 0) sẽ là không . Trừ khi một trình biên dịch có thể thay thế một lệnh gọi đến memmove () bằng một lệnh gọi đến memmoveAtLeastOneByte (), cách giải quyết để bảo vệ khỏi "tối ưu hóa" của các trình biên dịch thông minh / ngu ngốc nói chung sẽ dẫn đến một so sánh bổ sung mà trình biên dịch sẽ không thể loại bỏ.
supercat

-3

Không, memcpy(0,0,0)không an toàn. Thư viện tiêu chuẩn có thể sẽ không thất bại trong cuộc gọi đó. Tuy nhiên, trong môi trường thử nghiệm, một số mã bổ sung có thể xuất hiện trong memcpy () để phát hiện ghi đè bộ đệm và các sự cố khác. Và cách phiên bản đặc biệt của memcpy () phản ứng với con trỏ NULL, tốt, không được xác định.


Tôi không thể thấy bất kỳ điều gì mà câu trả lời của bạn thêm vào các câu trả lời hiện có đã chỉ ra rằng điều này không an toàn, với các đoạn trích từ tiêu chuẩn.
Matthieu M.

Tôi đã viết lý do, tại sao một số triển khai nhất định của memcpy ("trong môi trường thử nghiệm") có thể dẫn đến sự cố với memcpy (0, 0, 0). Tôi suy nghĩ, đó là điều mới.
Kai Petzke
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.