In con trỏ rỗng với% p là hành vi không xác định?


93

Có phải hành vi không xác định để in con trỏ rỗng với mã %pđịnh nghĩa chuyển đổi không?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

Câu hỏi áp dụng cho tiêu chuẩn C chứ không áp dụng cho việc triển khai C.


A không thực sự nghĩ rằng bất kỳ ai (kể cả ủy ban C) quan tâm quá nhiều đến nó. Đó là một vấn đề khá giả tạo, không (hoặc gần như không có) ý nghĩa thực tế.
P__J__

nó giống như printf chỉ hiển thị giá trị, và không chạm vào (nghĩa là đọc hoặc ghi đối tượng được trỏ) - không thể là UB i con trỏ có giá trị kiểu của nó (NULL là giá trị hợp lệ )
P__J__

3
@PeterJ hãy nói những gì bạn đang nói là đúng (mặc dù rõ ràng tiêu chuẩn quy định khác), thực tế là chúng ta đang tranh luận về điều này làm cho câu hỏi trở thành một câu hỏi hợp lệ và chính xác, vì có vẻ như phần được trích dẫn bên dưới của tiêu chuẩn làm cho rất khó hiểu đối với một nhà phát triển thông thường chuyện quái gì đang xảy ra .. Có nghĩa là: câu hỏi không đáng bị bỏ phiếu, vì vấn đề này cần được làm rõ!
Peter Varo


2
@PeterJ đó là một câu chuyện khác sau đó, cảm ơn vì đã làm rõ :)
Peter Varo

Câu trả lời:


93

Đây là một trong những trường hợp kỳ lạ khi chúng tôi phải tuân theo những hạn chế của ngôn ngữ tiếng Anh và cấu trúc không nhất quán trong tiêu chuẩn. Vì vậy, tốt nhất, tôi có thể đưa ra một lập luận phản bác thuyết phục, vì không thể chứng minh được điều đó :) 1


Mã trong câu hỏi thể hiện hành vi được xác định rõ ràng.

[7.1.4] là cơ sở của câu hỏi, hãy bắt đầu ở đó:

Mỗi phòng trong số báo cáo sau đây áp dụng trừ khi quy định rõ ràng trong các mô tả chi tiết tiếp theo: 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 lĩnh vực chức năng, hoặc một con trỏ ra ngoài không gian địa chỉ của chương trình, hoặc một con trỏ null , [... ví dụ khác ...] ) [...] hành vi là không xác định. [... các câu khác ...]

Đây là ngôn ngữ vụng về. Một cách hiểu là các mục trong danh sách là UB cho tất cả các chức năng của thư viện, trừ khi bị ghi đè bởi các mô tả riêng lẻ. Nhưng danh sách bắt đầu bằng "chẳng hạn như", cho thấy rằng nó mang tính minh họa, không đầy đủ. Ví dụ, nó không đề cập đến kết thúc null đúng của chuỗi (quan trọng đối với hành vi của ví dụ strcpy).

Vì vậy, rõ ràng mục đích / phạm vi của 7.1.4 chỉ đơn giản là "giá trị không hợp lệ" dẫn đến UB ( trừ khi có quy định khác ). Chúng ta phải xem xét mô tả của từng hàm để xác định những gì được coi là "giá trị không hợp lệ".

Ví dụ 1 - strcpy

[7.21.2.3] chỉ nói điều này:

Các strcpybản sao chức năng chuỗi trỏ đến bởi s2(bao gồm các ký tự null chấm dứt) vào mảng trỏ đến bởi s1. Nếu việc sao chép diễn ra giữa các đối tượng chồng lên nhau, hành vi đó là không xác định.

Nó không đề cập rõ ràng đến con trỏ null, nhưng nó cũng không đề cập đến dấu chấm dứt null. Thay vào đó, một suy luận từ "chuỗi được trỏ tới s2" rằng các giá trị hợp lệ duy nhất là chuỗi (tức là con trỏ đến mảng ký tự kết thúc bằng null).

Thật vậy, mô hình này có thể được nhìn thấy trong các mô tả riêng lẻ. Một số ví dụ khác:

  • [7.6.4.1 (fenv)] lưu trữ các môi trường nổi-điểm hiện tại trong đối tượng được trỏ đến bởienvp

  • [7.12.6.4 (frexp)] lưu trữ các số nguyên trong int tượng được trỏ đến bởiexp

  • [7.19.5.1 (fclose)] các dòng chỉ vào bởistream

Ví dụ 2 - printf

[7.19.6.1] nói điều này về %p:

p- Đối số sẽ là một con trỏ tới void. Giá trị của con trỏ được chuyển đổi thành một chuỗi các ký tự in, theo cách thức được triển khai xác định.

Null là một giá trị con trỏ hợp lệ và phần này không đề cập rõ ràng rằng null là một trường hợp đặc biệt, cũng không phải con trỏ phải trỏ vào một đối tượng. Vì vậy, nó được xác định hành vi.


1. Trừ khi một tác giả tiêu chuẩn đưa ra, hoặc trừ khi chúng tôi có thể tìm thấy điều gì đó tương tự như một tài liệu cơ sở làm rõ mọi thứ.


Nhận xét không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Bhargav Rao

1
"nhưng nó không đề cập đến các đầu cuối rỗng" là yếu trong Ví dụ 1 - strcpy vì thông số cho biết "sao chép chuỗi ". chuỗi được định nghĩa rõ ràng là có ký tự null .
Chux - Khôi phục Monica

1
@chux - Đó là quan điểm của tôi - người ta phải suy ra những gì hợp lệ / không hợp lệ từ ngữ cảnh, thay vì giả định rằng danh sách trong 7.1.4 là đầy đủ. (Tuy nhiên, sự tồn tại của khu vực này của câu trả lời của tôi có ý nghĩa hơi hơn trong bối cảnh ý kiến mà có kể từ khi bị xóa, cho rằng strcpy là một phản ví dụ.)
Oliver Charlesworth

1
Điểm mấu chốt của vấn đề là người đọc sẽ giải thích như thế nào . Nó có nghĩa là một số ví dụ về các giá trị có thể không hợp lệ ? Nó có nghĩa là một số ví dụ luôn là giá trị không hợp lệ ? Đối với hồ sơ, tôi đi với cách giải thích đầu tiên.
ninjalj

1
@ninjalj - Vâng, đã đồng ý. Về cơ bản, đó là những gì tôi đang cố gắng truyền đạt trong câu trả lời của mình ở đây, tức là "đây là những ví dụ về các loại điều có thể là giá trị không hợp lệ". :)
Oliver Charlesworth

20

Câu trả lời ngắn gọn

Vâng . Việc in con trỏ rỗng với mã %pđịnh nghĩa chuyển đổi có hành vi không xác định. Phải nói rằng, tôi không biết về bất kỳ triển khai tuân thủ nào hiện có sẽ hoạt động sai.

Câu trả lời áp dụng cho bất kỳ tiêu chuẩn C nào (C89 / C99 / C11).


Câu trả lời dài

Bộ %pđịnh nghĩa chuyển đổi mong đợi một đối số của con trỏ kiểu vô hiệu, việc chuyển đổi con trỏ thành các ký tự có thể in được do việc triển khai xác định. Nó không nói rằng con trỏ null được mong đợi.

Phần giới thiệu về các hàm thư viện chuẩn nêu rõ rằng các con trỏ null làm đối số cho các hàm (thư viện chuẩn) được coi là các giá trị không hợp lệ, trừ khi nó được quy định rõ ràng khác.

C99 / C11 §7.1.4 p1

[...] Nếu một đối số của một hàm có giá trị không hợp lệ (chẳng hạn như [...] con trỏ null, [...] thì hành vi là không xác định.

Ví dụ cho các hàm (thư viện chuẩn) yêu cầu con trỏ null làm đối số hợp lệ:

  • fflush() sử dụng con trỏ null để xả "tất cả các luồng" (áp dụng).
  • freopen() sử dụng con trỏ null để chỉ ra tệp "hiện được liên kết" với luồng.
  • snprintf() cho phép chuyển một con trỏ null khi 'n' bằng không.
  • realloc() sử dụng một con trỏ null để cấp phát một đối tượng mới.
  • free() cho phép chuyển một con trỏ null.
  • strtok() sử dụng một con trỏ null cho các cuộc gọi tiếp theo.

Nếu chúng ta xét trường hợp cho snprintf(), sẽ hợp lý khi cho phép truyền một con trỏ null khi 'n' bằng 0, nhưng điều này không đúng với các hàm (thư viện chuẩn) khác cho phép một 'n' bằng 0 tương tự. Ví dụ: memcpy(), memmove(), strncpy(), memset(), memcmp().

Nó không chỉ được chỉ định trong phần giới thiệu về thư viện chuẩn mà còn một lần nữa trong phần giới thiệu về các chức năng này:

C99 §7.21.1 p2 / C11 §7.24.1 p2

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 phần 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 một lệnh gọi như vậy sẽ vẫn có giá trị hợp lệ như được mô tả trong 7.1.4.


Có cố ý không?

Tôi không biết UB của %pvới một con trỏ null trên thực tế có cố ý hay không, nhưng vì tiêu chuẩn tuyên bố rõ ràng rằng con trỏ null được coi là giá trị không hợp lệ như là đối số của các hàm thư viện tiêu chuẩn và sau đó nó đi và chỉ định rõ ràng các trường hợp mà một null con trỏ là một đối số hợp lệ (snprintf, miễn phí, vv), và sau đó nó đi và một lần nữa lặp lại yêu cầu cho các đối số có giá trị ngay cả trong không trường hợp 'n' ( memcpy, memmove, memset), sau đó tôi nghĩ rằng nó là hợp lý để giả định rằng Ủy ban tiêu chuẩn C không quá quan tâm đến việc có những thứ như vậy không được xác định.


Nhận xét không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Bhargav Rao

1
@JeroenMostert: Mục đích của lập luận này là gì? Trích dẫn đưa ra của 7.1.4 khá rõ ràng, phải không? Có gì để tranh luận về "trừ khi được tuyên bố rõ ràng khác" khi nó không được tuyên bố khác? Có gì để tranh luận về thực tế là thư viện hàm chuỗi (không liên quan) có cách diễn đạt tương tự, vì vậy cách diễn đạt này dường như không phải là ngẫu nhiên? Tôi nghĩ rằng câu trả lời này (mặc dù không thực sự hữu ích trong thực tế ) như đúng như có thể được.
Damon

3
@Damon: Phần cứng thần thoại của bạn không phải là thần thoại, có rất nhiều kiến ​​trúc mà các giá trị không đại diện cho các địa chỉ hợp lệ có thể không được tải trong các thanh ghi địa chỉ. Tuy nhiên, việc chuyển con trỏ null làm đối số hàm để hoạt động trên các nền tảng đó như một cơ chế chung. Chỉ đặt một cái vào ngăn xếp sẽ không làm nổ tung mọi thứ.
Jeroen Mostert

1
@anatolyg: Trên bộ xử lý x86, địa chỉ có hai phần - một đoạn và một phần bù. Trên 8086, tải một thanh ghi phân đoạn cũng giống như tải bất kỳ thanh ghi nào khác, nhưng trên tất cả các máy sau này, nó tải một bộ mô tả phân đoạn. Việc tải một bộ mô tả không hợp lệ sẽ gây ra một cái bẫy. Tuy nhiên, rất nhiều mã cho bộ xử lý 80386 trở lên chỉ sử dụng một phân đoạn và do đó không bao giờ tải thanh ghi phân đoạn nào cả .
supercat

1
Tôi nghĩ rằng mọi người sẽ đồng ý rằng việc in một con trỏ null %pkhông được cho là hành vi không xác định
MM

-1

Các tác giả của Tiêu chuẩn C đã không cố gắng liệt kê đầy đủ tất cả các yêu cầu về hành vi mà việc triển khai phải đáp ứng để phù hợp với bất kỳ mục đích cụ thể nào. Thay vào đó, họ kỳ vọng rằng những người viết trình biên dịch sẽ thực hiện một số nhận thức chung nhất định cho dù Tiêu chuẩn có yêu cầu hay không.

Câu hỏi liệu một thứ gì đó gọi ra UB hiếm khi tự nó hữu ích. Các câu hỏi thực sự có tầm quan trọng là:

  1. Ai đó đang cố gắng viết một trình biên dịch chất lượng có nên làm cho nó hoạt động theo kiểu có thể đoán trước được không? Đối với kịch bản được mô tả, câu trả lời rõ ràng là có.

  2. Các lập trình viên có nên được quyền mong đợi rằng các trình biên dịch chất lượng cho bất kỳ thứ gì giống với các nền tảng bình thường sẽ hoạt động theo kiểu có thể đoán trước được không? Trong kịch bản được mô tả, tôi sẽ nói câu trả lời là có.

  3. Có thể một số người viết trình biên dịch ngu ngốc kéo dài cách giải thích của Tiêu chuẩn để biện minh cho việc làm điều gì đó kỳ lạ? Tôi hy vọng là không, nhưng sẽ không loại trừ nó.

  4. Trình biên dịch sanitizing có nên thắc mắc về hành vi này không? Điều đó sẽ phụ thuộc vào mức độ hoang tưởng của người dùng của họ; một trình biên dịch làm sạch có lẽ không nên mặc định là phản ứng dữ dội về hành vi như vậy, nhưng có lẽ cung cấp một tùy chọn cấu hình để thực hiện trong trường hợp các chương trình có thể được chuyển sang trình biên dịch "thông minh" / ngu ngốc hoạt động kỳ lạ.

Nếu một cách giải thích hợp lý của Tiêu chuẩn sẽ ngụ ý một hành vi được xác định, nhưng một số người viết trình biên dịch kéo dài cách giải thích để biện minh cho việc làm khác, thì liệu Tiêu chuẩn nói có thực sự quan trọng không?


1. Không có gì lạ khi các lập trình viên nhận thấy các giả định được đưa ra bởi các trình tối ưu hóa hiện đại / tích cực trái ngược với những gì họ cho là "hợp lý" hoặc "chất lượng". 2. Khi nói đến sự mơ hồ trong đặc điểm kỹ thuật, không có gì lạ khi những người triển khai mâu thuẫn về quyền tự do mà họ có thể giả định. 3. Khi nói đến các thành viên của Ủy ban tiêu chuẩn C, thậm chí họ không luôn luôn đồng ý như những gì giải thích 'đúng' được, chứ chưa nói gì nó nên được. Với những điều đã nói ở trên, chúng ta nên tuân theo cách giải thích hợp lý của ai?
Dror K.

6
Trả lời câu hỏi "đoạn mã cụ thể này có gọi UB hay không" với một luận văn về suy nghĩ của bạn về tính hữu ích của UB hoặc cách trình biên dịch phải xử lý là một nỗ lực kém cho một câu trả lời, đặc biệt là vì bạn có thể sao chép và dán nó như một câu trả lời cho hầu hết mọi câu hỏi về UB cụ thể. Như một lời chào mừng cho sự phát triển mạnh mẽ của bạn: vâng, nó thực sự quan trọng những gì Tiêu chuẩn nói, bất kể một số người viết trình biên dịch làm gì hoặc bạn nghĩ gì về họ khi làm điều đó, bởi vì Tiêu chuẩn là thứ mà cả người lập trình và người viết trình biên dịch đều bắt đầu.
Jeroen Mostert

1
@JeroenMostert: Câu trả lời cho "X có gọi hành vi không xác định không" thường sẽ phụ thuộc vào ý nghĩa của câu hỏi. Nếu một chương trình được coi là có Hành vi không xác định nếu Tiêu chuẩn sẽ không áp đặt yêu cầu nào đối với hành vi của việc triển khai tuân thủ, thì gần như tất cả các chương trình đều gọi UB. Các tác giả của Tiêu chuẩn rõ ràng cho phép các triển khai hoạt động theo kiểu tùy ý nếu một chương trình tổ chức hàm gọi quá sâu, miễn là một triển khai có thể xử lý chính xác ít nhất một văn bản nguồn (có thể có nội dung) thực hiện các giới hạn dịch trong Stadard.
supercat

@supercat: rất thú vị, nhưng có phải là printf("%p", (void*) 0)hành vi không xác định hay không, theo Tiêu chuẩn? Các lệnh gọi hàm lồng nhau có liên quan đến điều này giống như giá chè ở Trung Quốc. Và vâng, UB rất phổ biến trong các chương trình thế giới thực - nó là gì?
Jeroen Mostert

1
@JeroenMostert: Vì Tiêu chuẩn sẽ cho phép một triển khai có lỗi coi hầu hết mọi chương trình đều có UB, điều quan trọng sẽ là hành vi của các triển khai không có lỗi. Trong trường hợp bạn không để ý, tôi không chỉ viết một bản sao / dán về UB mà còn trả lời câu hỏi về %ptừng ý nghĩa có thể có của câu hỏi.
supercat
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.