2012-04-17 36 views
18

Xét đoạn mã sau (nó xuất hiện như là kết quả của this discussion):tương đương của p [0] và * p với nhiều loại mảng không đầy đủ

#include <stdio.h> 

void foo(int (*p)[]) {   // Argument has incomplete array type 
    printf("%d\n", (*p)[1]); 
    printf("%d\n", p[0][1]); // Line 5 
} 

int main(void) { 
    int a[] = { 5, 6, 7 }; 
    foo(&a);     // Line 10 
} 

GCC 4.3.4 complains với thông báo lỗi:

prog.c: In function ‘foo’: 
prog.c:5: error: invalid use of array with unspecified bounds 

Thông báo lỗi tương tự trong GCC 4.1.2 và dường như là bất biến của -std=c99, -Wall, -Wextra.

Vì vậy, nó không hài lòng với biểu thức p[0], nhưng nó hài lòng với *p, mặc dù những điều này nên (theo lý thuyết) là tương đương. Nếu tôi nhận xét ra dòng 5, mã biên dịch và làm những gì tôi sẽ "mong đợi" (hiển thị 6).

Có lẽ một trong những điều sau đây là đúng:

  1. sự hiểu biết của tôi về tiêu chuẩn C (s) là không chính xác, và các biểu thức không phải là tương đương.
  2. GCC có một lỗi.

Tôi muốn đặt tiền của tôi trên (1).

Câu hỏi: Mọi người có thể giải thích về hành vi này không?

Làm rõ: Tôi biết rằng điều này có thể được "giải quyết" bằng cách chỉ định kích thước mảng trong định nghĩa hàm. Đó không phải là những gì tôi quan tâm đến


Đối với điểm "tiền thưởng":. bất cứ ai có thể xác nhận rằng MSVC 2010 là do lỗi khi từ chối dòng 10 với thông điệp sau đây?

1><snip>\prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]' 
+0

Kể từ khi là (* p) [] dấu * mảng * loại biểu hiện, hoàn chỉnh hoặc bằng cách khác? –

+0

@JohnBode: Vâng, nếu chúng ta có 'int (* p) [10] ', sau đó' (* p) 'sẽ là kiểu mảng. Cho rằng chúng tôi không có kích thước, tôi đoán rằng làm cho nó không đầy đủ. –

Trả lời

12

Mục 6.5.2.1 của n1570, Array subscripting:

chế

Một trong những biểu thức có trách nhiệm gõ '' con trỏ để hoàn thành loại đối tượng '', khác biểu thức phải có kiểu số nguyên và kết quả có loại '' type ''.

Vì vậy, tiêu chuẩn cấm biểu thức p[0] nếu p là con trỏ đến loại không đầy đủ. Không có giới hạn nào đối với nhà điều hành vô hướng *.

Trong các phiên bản/bản thảo cũ của tiêu chuẩn, tuy nhiên, (n1256 và C99), từ "hoàn thành" vắng mặt trong đoạn đó. Không được tham gia vào bất kỳ cách nào trong thủ tục tiêu chuẩn, tôi chỉ có thể đoán xem đó là một thay đổi phá vỡ hay sửa chữa một thiếu sót. Các hành vi của trình biên dịch cho thấy sau này.Điều đó được củng cố bởi thực tế là p[i] là theo tiêu chuẩn giống hệt với *(p + i) và biểu thức thứ hai không có ý nghĩa đối với con trỏ đến loại không đầy đủ, vì vậy đối với p[0] để hoạt động nếu p là con trỏ đến loại không đầy đủ trường hợp sẽ là cần thiết.

+1

Phiên bản tiêu chuẩn nào? Bản nháp C99 của tôi không có từ "hoàn chỉnh". Ngoài ra, từ ngữ tương đương trong phiên bản của bạn cho toán tử gián tiếp là gì? –

+0

Đó là n1570. Hãy để tôi xem xét các phiên bản cũ hơn. –

+0

Thú vị. 1 trong khi chờ đợi, nhưng nếu bạn có thể làm sáng tỏ liệu đây có phải là sự thay đổi đột phá trong tiêu chuẩn (không), hoặc chỉ là thiếu sót trong các phiên bản cũ hơn, thì tôi chắc chắn sẽ chấp nhận câu trả lời này khi tôi thức lên vào ngày mai! –

4

C của tôi là một chút gỉ, nhưng đọc sách của tôi là khi bạn có một int (*p)[] này:

(*p)[n] 

Says "dereference p để có được một mảng ints, sau đó lấy một thứ n". Mà dường như tự nhiên để được xác định rõ. Trong khi điều này:

p[n][m] 

Nói "lấy mảng thứ n trong p, sau đó lấy phần tử thứ m của mảng đó". Mà dường như không được xác định rõ ràng; bạn phải biết các mảng lớn như thế nào để tìm vị trí thứ n bắt đầu.

Điều này có thể hoạt động cho trường hợp đặc biệt cụ thể trong đó n = 0, vì mảng thứ 0 dễ tìm bất kể mảng lớn như thế nào. Bạn đã thấy rằng GCC không nhận ra trường hợp đặc biệt này. Tôi không biết chi tiết về ngôn ngữ, vì vậy tôi không biết đó có phải là "lỗi" hay không, nhưng sở thích cá nhân của tôi trong thiết kế ngôn ngữ là p[n][m] nên hoạt động hay không, không phải là nó hoạt động khi n là tĩnh được biết là 0 và không khác.

Có phải *p <===> p[0] thực sự là quy tắc dứt khoát từ đặc tả ngôn ngữ hay chỉ là quan sát? Tôi không nghĩ rằng của cuộc hội thảo và lập chỉ mục theo số không là hoạt động tương tự khi tôi lập trình.

+0

Đặc tả ngôn ngữ nói rằng 'p [0] === * (p + 0)'. Nếu kích thước của kiểu trỏ tới được biết, đó là tất nhiên giống như '* p', nhưng với kích thước không xác định, nó sẽ cần một vỏ đặc biệt cho 0 để hoạt động. –

+0

Vâng, khi bạn nhìn vào những gì đang diễn ra trên mui xe, điều đó làm cho cảm giác hoàn hảo rằng chúng sẽ khác nhau. Tôi đoán trực giác của tôi bị mờ bởi thực tế là trong hầu hết các trường hợp *, chúng tôi xử lý '* p' và' p [0] 'như thể chúng tương đương về mặt logic. –

4

Đối với câu hỏi "điểm thưởng" của bạn (có thể bạn đã hỏi câu hỏi này dưới dạng câu hỏi riêng), MSVC10 bị lỗi. Lưu ý rằng MSVC chỉ thực hiện C89, vì vậy tôi đã sử dụng tiêu chuẩn đó.

Đối với các cuộc gọi chức năng, C89 §3.3.2.2 cho chúng ta biết:

Mỗi đối số phải có một loại sao cho giá trị của nó có thể được gán cho một đối tượng với phiên bản không đủ tiêu chuẩn của các loại của nó thông số tương ứng.

Các hạn chế chuyển nhượng trong C89 §3.3.16:

Một trong những điều sau đây sẽ tổ chức: ... cả hai toán hạng là con trỏ đến phiên bản đủ điều kiện hoặc không đủ tiêu chuẩn của các loại tương thích, và loại được trỏ sang bên trái có tất cả các loại vòng loại được trỏ đến ở bên phải;

Vì vậy, chúng tôi có thể gán hai con trỏ (và do đó gọi hàm bằng tham số con trỏ bằng đối số con trỏ) nếu hai con trỏ trỏ tới các loại tương thích.

Sự phù hợp của loại mảng khác nhau được quy định tại C89 §3.5.4.2:

Đối với hai loại mảng để tương thích, cả hai sẽ có loại yếu tố tương thích, và nếu cả hai specifiers kích thước có mặt, họ có cùng giá trị.

Đối với hai loại mảng int []int [3] thì điều kiện này được giữ rõ ràng. Do đó cuộc gọi hàm là hợp pháp.

+0

+1 cho điều này. Và tôi đồng ý, đây chắc hẳn là một câu hỏi riêng. –

0
void foo(int (*p)[]) 
{ 
    printf("%d\n", (*p)[1]); 
    printf("%d\n", p[0][1]); // Line 5 
} 

Ở đây, p là một con trỏ đến một mảng của một số không xác định int s. *p truy cập mảng đó, vì vậy (*p)[1] là phần tử thứ 2 trong mảng.

p[n] thêm p và n lần so với kích thước của nhọn để mảng, đó là chưa biết. Ngay cả trước khi xem xét [1], nó bị hỏng. Đúng là 0 lần bất cứ thứ gì vẫn là 0, nhưng trình biên dịch rõ ràng là kiểm tra tính hợp lệ của tất cả các thuật ngữ mà không bị đoản mạch ngay khi nó thấy không. Vì vậy ...

Vì vậy, nó không hài lòng với biểu thức p [0], nhưng điều đó thật hài lòng với * p, mặc dù chúng nên (tương đương) tương đương.

Như đã giải thích, họ rõ ràng là không tương đương ... suy nghĩ của p[0] như p + 0 * sizeof *p và nó rõ ràng lý do tại sao ....

Đối với điểm "tiền thưởng": bất cứ ai có thể xác nhận rằng MSVC 2010 là lỗi khi từ chối dòng 10 với thông báo sau? 1> \ prog.c (10): C4048 cảnh báo: subscript mảng khác nhau: 'int () []' và 'int () [3]'

Visual C++ (và trình biên dịch khác) tự do cảnh báo về những điều mà họ cho là không thực hành tốt, những thứ đã được tìm thấy theo kinh nghiệm thường sai lầm, hoặc những thứ mà các nhà biên dịch chỉ có một sự ngờ vực không hợp lý, ngay cả khi họ hoàn toàn hợp pháp với Tiêu chuẩn ... . Các ví dụ mà bạn có thể quen thuộc bao gồm "So sánh chữ ký và unsigned" và "nhiệm vụ trong một điều kiện (đề nghị xung quanh với ngoặc thêm)"

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