2011-02-05 36 views
11

Mục §6.5.3.2 "Địa chỉ và gián tiếp khai thác" ¶3 nói (phần có liên quan chỉ):tiêu chuẩn C giải quyết đơn giản hóa mâu thuẫn

Các & hành unary trả về địa chỉ của toán hạng của nó. ... Nếu toán hạng là kết quả của toán tử * unary, toán tử & được đánh giá và kết quả là nếu cả hai bị bỏ qua, ngoại trừ các ràng buộc trên toán tử vẫn áp dụng và kết quả không phải là lvalue. Tương tự, nếu các toán hạng là kết quả của một nhà điều hành [], cả & điều hành cũng không phải là unary * được ngụ ý bởi [] được đánh giá và kết quả là, nếu như các nhà điều hành & đã được gỡ bỏ và các nhà điều hành [] được thay đổi thành một nhà điều hành + . ...

Điều này có nghĩa rằng đây:

#define NUM 10 
int tmp[NUM]; 
int *i = tmp; 
printf("%ti\n", (ptrdiff_t) (&*i - i)); 
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i)); 

có nên hoàn toàn hợp pháp, in 0 và NUM (10). Tiêu chuẩn này có vẻ rất rõ ràng rằng cả hai trường hợp này đều được yêu cầu phải được tối ưu hóa.

Tuy nhiên, nó dường như không đòi hỏi những điều sau đây để được tối ưu hóa:

struct { int a; short b; } tmp, *s = tmp; 
printf("%ti\n", (ptrdiff_t) (&s->b - s)); 

Điều này có vẻ hết sức mâu thuẫn. Tôi có thể thấy không có lý do gì mà mã trên không nên in sizeof(int) cộng với (không) đệm (có thể là 4).

Đơn giản hóa biểu thức &-> sẽ giống như khái niệm (IMHO) là &[], một địa chỉ đơn giản-cộng-bù. Nó thậm chí là một bù đắp mà sẽ được xác định tại thời gian biên dịch, chứ không phải là thời gian chạy tiềm năng với các nhà điều hành [].

Có điều gì lý do tại sao điều này dường như không nhất quán?

+0

Tôi đã nhìn thấy rất nhiều câu hỏi về tiêu chuẩn C và C++ mà tôi không biết cách làm bất cứ điều gì nhưng đề cao, yêu thích và chờ đợi để đọc câu trả lời. Nó cảm thấy kỳ lạ để thực sự đã viết một. –

+0

thú vị ... Bản in MSVC++ 4! – Abhi

+0

@Abhi Rao - GCC (4.0) với -Wall -Wextra -Werror biên dịch và in 4 mà không có khiếu nại. –

Trả lời

4

Trong ví dụ của bạn, &i[10] thực sự không hợp pháp: nó trở thành i + 10, trở thành NULL + 10 và bạn không thể thực hiện số học trên con trỏ rỗng. (6.5.6/8 liệt kê các điều kiện theo đó số học con trỏ có thể được thực hiện)

Dù sao, quy tắc này đã được thêm vào C99; nó không có mặt trong C89. Theo tôi hiểu rằng nó đã được thêm vào trong phần lớn để làm cho mã như sau đây được xác định rõ:

int* begin, * end; 
int v[10]; 

begin = &v[0]; 
end = &v[10]; 

Đó dòng cuối cùng là về mặt kỹ thuật không hợp lệ trong C89 (và trong C++) nhưng được cho phép trong C99 vì quy tắc này. Đó là một sự thay đổi tương đối nhỏ đã tạo nên một cấu trúc được sử dụng phổ biến.

Vì bạn không thể thực hiện số học trên một con trỏ rỗng, ví dụ của bạn (&s->b) sẽ không hợp lệ.

Đối với lý do tại sao có "sự mâu thuẫn" này, tôi chỉ có thể đoán được. Có khả năng là không ai nghĩ rằng nó phù hợp hoặc không ai nhìn thấy một trường hợp sử dụng thuyết phục cho việc này. Có thể điều này đã được xem xét và cuối cùng bị từ chối. Không có nhận xét nào về việc giảm số &* trong the Rationale. Bạn có thể tìm thấy một số thông tin dứt khoát trong the WG14 papers, nhưng tiếc là chúng dường như được sắp xếp khá kém, vì vậy việc rà soát thông tin có thể tẻ nhạt.

+0

Tôi lấy các con trỏ rỗng ra khỏi các ví dụ, vì chúng chưa bao giờ thực sự là những gì tôi quan tâm. –

+0

Tôi không thấy làm thế nào 'NULL' đi vào chơi ở tất cả ở đây. Ngoài ra, đối với số học con trỏ (miễn là bạn không đánh giá đối tượng không tồn tại), phần tử chỉ sau khi một mảng có thể được sử dụng. AFAIR, điều này được đề cập ở một số nơi của tiêu chuẩn. –

+0

@Jens: Các ví dụ ban đầu trong câu hỏi được sử dụng con trỏ 'NULL' và không có số học được xác định rõ có thể được thực hiện trên một con trỏ null. Bạn có thể có được một con trỏ đến phần tử "một-qua-the-end", nhưng bạn không thể dereference nó. Đối với 'int v [10];', chỉ trong C99 là hợp pháp để sử dụng '& v [10]' hoặc '& * (v + 10)'; trong C++ và C90 mã như vậy chính thức mang lại hành vi không xác định. –

1

Tôi tin rằng trình biên dịch có thể chọn đóng gói theo nhiều cách khác nhau, có thể thêm phần đệm giữa các thành viên của cấu trúc để tăng tốc độ truy cập bộ nhớ. Điều này có nghĩa là bạn không thể chắc chắn nói rằng b sẽ luôn là là một khoảng cách là 4. Giá trị duy nhất không có cùng một vấn đề. Ngoài ra, trình biên dịch có thể không biết bố cục của cấu trúc trong bộ nhớ trong giai đoạn tối ưu hóa, do đó ngăn chặn bất kỳ loại tối ưu hóa nào liên quan đến truy cập cấu trúc thành viên và phôi con trỏ tiếp theo.


chỉnh sửa:

Tôi có lý thuyết khác ...

nhiều lần so với trình biên dịch sẽ tối ưu hóa cây cú pháp trừu tượng chỉ sau khi phân tích từ vựng và phân tích cú pháp. Điều này có nghĩa là nó sẽ tìm thấy những thứ như các toán tử hủy bỏ và các biểu thức đánh giá một hằng số và giảm các phần đó của cây thành một nút. Điều này cũng có nghĩa là thông tin về cấu trúc không khả dụng. các lần tối ưu hóa sau này xảy ra sau khi một số thế hệ mã có thể tính đến điều này vì chúng có thêm thông tin, nhưng đối với những thứ như cắt tỉa AST, thông tin đó vẫn chưa có.

+1

Bạn không thể chắc chắn nó sẽ luôn luôn là một bù đắp của 4, nhưng đối với một 'struct' là hữu ích bạn có thể chắc chắn nó sẽ là một bù đắp không đổi. Và tôi đã sử dụng một 'int' theo sau là một' short', vì vậy tôi nghi ngờ có một trình biên dịch cần đặt đệm vào giữa chúng. –

+0

"Ngoài ra, trình biên dịch có thể không biết bố cục của cấu trúc trong bộ nhớ trong giai đoạn tối ưu hóa ..." Điều đó có vẻ giống như một số thông tin khá cần thiết cho trình tối ưu hóa. –

+0

tôi nghĩ rằng nó cũng sẽ phụ thuộc vào cách trình biên dịch được viết. Tiêu chuẩn quy định các quy tắc về cách thức hoạt động bình thường, nhưng việc đặt cờ tối ưu hóa có thể làm như bạn nói.Tôi đoán là các nhà văn của tiêu chuẩn không muốn áp đặt tối ưu hóa quá nhiều. –

2

Tôi nghĩ rằng quy tắc chưa được thêm vào cho mục đích tối ưu hóa (nó mang lại điều gì nếu quy tắc if-không?) Nhưng để cho phép &t[sizeof(t)/sizeof(*t)]&*(t+sizeof(t)/sizeof(*t)) sẽ là hành vi không xác định mà không có nó trực tiếp có vẻ ngớ ngẩn, nhưng thêm một hoặc hai lớp macro và nó có thể có ý nghĩa). Tôi không thấy trường hợp vỏ đặc biệt & p-> m sẽ mang lại lợi ích như vậy. Lưu ý rằng như James đã chỉ ra, &p[10] với p một con trỏ null vẫn là hành vi không xác định; &p->m với p một con trỏ null tương tự sẽ vẫn không hợp lệ (và tôi phải thừa nhận rằng tôi không thấy bất kỳ sử dụng nào khi p là con trỏ rỗng).

+0

Sử dụng rõ ràng (IMHO) khi 'p = NULL' là việc thực hiện hack của macro' offsetof', dựa vào '& ((struct t *) 0) -> m' làm việc. Tuy nhiên, nó có thể dễ dàng thay đổi thành '1' (hoặc một giá trị con trỏ hợp lệ phụ thuộc vào trình biên dịch), thay vào đó là' 0', và trong khi nó có thể sẽ không cho bạn 'struct' tốt giá trị nó phải cung cấp cho bạn giá trị bù đắp đúng. –

+0

@ Chris: một aeon trước đây, tôi đã có một trình biên dịch C chuẩn được định nghĩa offsetof() về địa chỉ 0 và sau đó đưa ra các khối lõi hoặc lỗi biên dịch (tôi quên cái nào, bây giờ) khi nó được sử dụng. Tôi đã kết thúc hack tiêu đề hệ thống và sử dụng 1024 làm địa chỉ thay vì 0; mà làm việc tốt. Nó (1024) được căn chỉnh đủ để không đưa ra các vấn đề - không giống như 1. –

+0

Ngoại trừ các mảng ký tự, '& t [sizeof (t)]' vượt xa kết thúc của đối tượng được cấp phát. –

Các vấn đề liên quan