2015-02-18 13 views
10

Tôi đang trải qua một sự đột biến quan tâm đến hệ thống loại biến đổi của C99. Câu hỏi này được lấy cảm hứng từ this one.Khả năng tương thích các loại được sửa đổi khác nhau và các tác động an ninh của nó

Kiểm tra mã từ câu hỏi này, tôi đã khám phá điều gì đó thú vị. Hãy xem xét mã này:

int myFunc(int, int, int, int[][100]); 

int myFunc(int a, int b, int c, int d[][200]) { 
    /* Some code here... */ 
} 

Điều này rõ ràng sẽ không (và không) biên dịch. Tuy nhiên, mã này:

int myFunc(int, int, int, int[][100]); 

int myFunc(int a, int b, int c, int d[][c]) { 
    /* Some code here... */ 
} 

biên dịch thậm chí không có cảnh báo (trên gcc).

Điều đó dường như ngụ ý rằng một loại mảng biến đổi có thể thay đổi tương thích với bất kỳ loại mảng không thay đổi nào khác!

Nhưng đó không phải là tất cả. Bạn sẽ mong đợi một loại biến đổi có thể thay đổi ít nhất là bận tâm với biến nào được sử dụng để thiết lập kích thước của nó. Nhưng nó không có vẻ như vậy!

int myFunc(int, int b, int, int[][b]); 

int myFunc(int a, int b, int c, int d[][c]) { 
    return 0; 
} 

Cũng biên dịch mà không có bất kỳ lỗi nào.

Vì vậy, câu hỏi của tôi là: hành vi chuẩn hóa này có chính xác không?

Ngoài ra, nếu loại mảng biến đổi có thể thực sự tương thích với bất kỳ mảng nào có cùng kích thước, điều này có nghĩa là vấn đề bảo mật khó chịu không? Ví dụ: hãy xem xét mã sau:

int myFunc(int a, int b, int c, int d[][c]) { 
    printf("%d\n", sizeof(*d)/sizeof((*d)[0])); 
    return 0; 
} 

int main(){ 
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 
    myFunc(0, 0, 100, &arr); 

    return 0; 
} 

Biên dịch và kết quả đầu ra 100, không có lỗi hoặc cảnh báo, không có gì. Như tôi thấy, điều đó có nghĩa là mảng ngoài phạm vi dễ dàng viết ngay cả khi bạn đang kiểm tra nghiêm ngặt kích thước mảng của mình qua sizeof, không thực hiện một dàn diễn viên và thậm chí có tất cả cảnh báo được bật! Hay tôi đang thiếu một cái gì đó?

+0

Nếu bạn chưa làm như vậy, hãy thử thêm -std = c99-sai số-lỗi vào dòng biên dịch gcc của bạn và xem điều đó có tạo nên sự khác biệt nào không. – jschultz410

+0

@ jschultz410: ý tưởng hay, nhưng không có sự khác biệt nào = ( – Mints97

+0

Có nhiều trường hợp không thể cho trình biên dịch suy luận tĩnh giá trị của c (ví dụ - c là đầu vào từ stdin). nó thường không thể thực hiện bất kỳ loại kiểm tra tĩnh nào có ý nghĩa trên các tham số của định nghĩa hàm như vậy. Có vẻ như nếu bạn làm điều này, thì trình biên dịch sẽ nói "OK, tôi sẽ cho phép bạn chuyển bất cứ thứ gì bạn muốn làm d, " – jschultz410

Trả lời

4

C99, phần 6.7.5.2 dường như là nơi các quy tắc có liên quan được đưa ra. Đặc biệt,

Dòng 6:

Đối với hai loại mảng để tương thích, cả hai sẽ có các loại yếu tố tương thích, và nếu cả hai specifiers kích thước có mặt, và là số nguyên biểu thức hằng số, sau đó cả hai specifiers kích thước thì tùy theo tính có cùng giá trị không đổi.Nếu hai kiểu mảng được sử dụng trong một ngữ cảnh yêu cầu chúng phải tương thích, thì đó là hành vi không xác định nếu hai tham số kích thước đánh giá các giá trị không bằng nhau.

Câu trả lời trước đó, hiện đã bị xóa cũng được tham chiếu dòng 6. Bình luận về câu trả lời cho rằng câu thứ hai là tùy thuộc vào điều kiện ở cuối đầu tiên, nhưng điều đó dường như không đọc. Ví dụ 3 của phần đó có thể làm rõ (trích đoạn):

int c[n][n][6][m]; 
int (*r)[n][n][n+1]; 
r=c; // compatible, but defined behavior only if 
     // n == 6 and m == n+1 

Đó dường như so sánh với ví dụ trong câu hỏi: hai loại mảng, một có một chiều hướng liên tục và người kia có một chiều hướng biến tương ứng, và yêu cầu phải có tương thích. Hành vi không xác định (mỗi nhận xét trong ví dụ 3 và một cách đọc hợp lý là 6.7.5.2/6) khi thời gian chạy biến thứ nguyên khác với thứ nguyên hằng số biên dịch. Và không phải là hành vi không xác định những gì bạn mong đợi không? Khác tại sao nêu câu hỏi?

Giả sử chúng tôi có thể đồng ý rằng hành vi không xác định khi sự không khớp xảy ra, tôi quan sát rằng các trình biên dịch nói chung không bắt buộc phải nhận biết hành vi không xác định hoặc có thể không xác định, cũng như không phát hành bất kỳ loại chẩn đoán nào nếu chúng nhận ra điều đó. Tôi hy vọng trong trường hợp này trình biên dịch sẽ có khả năng cảnh báo về hành vi có thể không xác định, nhưng nó phải biên dịch thành công mã vì nó là cú pháp chính xác và đáp ứng tất cả các ràng buộc áp dụng. Lưu ý rằng trình biên dịch có khả năng cảnh báo về việc sử dụng như vậy có thể không làm như vậy theo mặc định.

+0

Cảm ơn, đó là một câu trả lời tuyệt vời! Tôi nghĩ bạn nói đúng, việc đọc quy tắc này của tôi có lẽ không chính xác. Tôi có lẽ quá quen với việc gõ không tương thích là UB gây ra Number One ... Điều đó để lại tiêu chuẩn C trong rõ ràng, nhưng không gcc =) anyway, ranting về nó không cho cảnh báo có lẽ sẽ không làm tốt ... – Mints97

+0

Và nếu nó đã được vào tôi, tôi tuyên bố rõ ràng các loại máy ảo không tương thích với tất cả mọi thứ chỉ để chắc chắn =) Làm cho tinh thần, IMO. – Mints97

+1

Nhưng @Mints, các loại VM phải tương thích ít nhất với chính mình, nếu không chúng sẽ không sử dụng được. Bạn vẫn có thể có hành vi không xác định với các kiểu máy ảo có cùng khai báo giống nhau, tuy nhiên, khi thời gian chạy, các tham số biến khác nhau. Cho rằng vấn đề, những gì để đạt được bằng cách làm cho các loại VM tự động không tương thích với mọi thứ khác?Đây là một ví dụ khác của C cho các công cụ lập trình mạnh mẽ, mà anh ta có thể gây ra thiệt hại mạnh. C không phải là cho weenies. (Không ngụ ý bất cứ điều gì ở đó.) –

-1
#include <stdio.h> 

void foo(int c, char d[][c]) 
{ 
    fprintf(stdout, "c = %d; d = %p; d + 1 = %p\n", c, d, d + 1); 
} 

int main() 
{ 
    char x[2][4]; 
    char y[3][16]; 
    char (*z)[4] = y; /* Warning: incompatible types */ 

    foo(4, x); 
    foo(16, y); 
    foo(16, x);  /* We are lying about x. What can/should the compiler/code do? */ 
    foo(4, y);   /* We are lying about y. What can/should the compiler/code do? */ 

    return 0; 
} 

Đầu ra:

c = 4; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b74 
c = 16; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b50 
c = 16; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b80 
c = 4; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b44 

Vì vậy, foo() không tự động tìm ra cách xa để thăng tiến d dựa trên c, như mã của bạn cũng chứng tỏ.

Tuy nhiên, trình biên dịch sẽ xác định tĩnh nếu/khi bạn gọi foo() không chính xác. Dường như nếu bạn làm điều này, thì trình biên dịch sẽ nói "OK, tôi sẽ cho phép bạn chuyển bất cứ thứ gì bạn muốn làm d, miễn là loại của nó là một mảng ký tự được lập chỉ mục gấp đôi. Các thao tác trên con trỏ d sẽ được xác định bởi c. Chúc may mắn! " Có nghĩa là, có, trình biên dịch thường không thể thực hiện kiểm tra kiểu tĩnh trên các kiểu tham số này và vì vậy tiêu chuẩn gần như chắc chắn không bắt buộc các trình biên dịch bắt tất cả các trường hợp có thể xác định tĩnh một kiểu không tương thích.

+0

Có, vì 'sizeof (* d)' được tính bằng 'c', vì nó là s hown trong câu hỏi của tôi =) – Mints97

+0

"Tuy nhiên, nó thường là không thể cho trình biên dịch để xác định nếu/khi bạn gọi foo() không chính xác". Đó chính là vấn đề. Tại sao tôi thậm chí còn được phép gọi 'foo' nếu điều này hoàn toàn giết chết việc kiểm tra kiểu tĩnh? Ngoài ra, việc kiểm tra kiểu động có thể được sử dụng. Việc thực thi VLA sẽ phải giữ kích thước của nó như một dạng siêu dữ liệu nào đó cho các cuộc gọi 'sizeof' động, vậy tại sao không, nói, segfault hoặc đi UB nếu kích thước này không khớp với kích thước của kiểu tham số nó đã được thông qua như? – Mints97

+2

@ Mints97: C không yêu cầu kiểm tra kiểu động. Nếu kích thước không khớp, hành vi không xác định; nó * không *, như bạn nói, "đi UB". Hành vi không xác định không có nghĩa là chương trình của bạn sẽ bị lỗi. Nó có nghĩa là hành vi là không xác định. Điều đó thường bao gồm các chương trình xuất hiện để "làm việc". –

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