2013-07-23 44 views
14

Tôi đang đọc this code from here (bằng tiếng Trung Quốc). Có một đoạn mã về thử nghiệm biến toàn cục trong C. Biến số a đã được định nghĩa trong tệp t.h đã được bao gồm hai lần. Trong tệp foo.c được xác định là struct b với một số giá trị và hàm main. Trong tệp main.c, đã xác định hai biến không được khởi tạo.C cùng một biến toàn cục được xác định trong các tệp khác nhau

/* t.h */ 
#ifndef _H_ 
#define _H_ 
int a; 
#endif 

/* foo.c */ 
#include <stdio.h> 
#include "t.h" 

struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

int main(); 

void foo() 
{ 
    printf("foo:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n", 
     &a, &b, sizeof b, b.a, b.b, main); 
} 

/* main.c */ 
#include <stdio.h> 
#include "t.h" 

int b; 
int c; 

int main() 
{ 
    foo(); 
    printf("main:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \t(&c)=0x%08x\n\tsize(b)=%d\n\tb=%d\n\tc=%d\n", 
     &a, &b, &c, sizeof b, b, c); 
    return 0; 
} 

Sau khi sử dụng Ubuntu GCC 4.4.3 biên dịch, kết quả là như thế này dưới đây:

foo: (&a)=0x0804a024 
    (&b)=0x0804a014 
    sizeof(b)=8 
    b.a=2 
    b.b=4 
    main:0x080483e4 
main: (&a)=0x0804a024 
    (&b)=0x0804a014 
    (&c)=0x0804a028 
    size(b)=4 
    b=2 
    c=0 

Biến ab có cùng một địa chỉ trong hai chức năng, nhưng kích thước của b đã thay đổi. Tôi không thể hiểu nó hoạt động như thế nào!

+3

Câu hỏi của bạn là gì? –

+2

Sử dụng '% p' để in con trỏ, bạn cũng nên thêm' foo' vào tiêu đề của mình. – Nobilis

+0

Nếu bạn muốn giải quyết xung đột, hãy khai báo biến tĩnh. – snf

Trả lời

19

Bạn đang vi phạm "một quy tắc định nghĩa" C, và kết quả là hành vi không xác định."Quy tắc một định nghĩa" không được nêu chính thức trong tiêu chuẩn như vậy. Chúng tôi đang xem xét các đối tượng trong các tệp nguồn khác nhau (còn gọi là đơn vị dịch), vì vậy chúng tôi quan tâm đến "định nghĩa bên ngoài". Cơ chế "một định nghĩa bên ngoài" ngữ nghĩa được nêu ra (C11 6,9 p5):

Một định nghĩa bên ngoài là một tuyên bố bên ngoài mà còn là một định nghĩa của một hàm (trừ một định nghĩa inline) hoặc một đối tượng. Nếu một định danh được khai báo với liên kết bên ngoài được sử dụng trong một biểu thức (không phải là một phần của toán hạng của toán tử sizeof hoặc _Alignof có kết quả là hằng số nguyên), một nơi nào đó trong toàn bộ chương trình sẽ có chính xác một định nghĩa bên ngoài; nếu không, sẽ không có nhiều hơn một.

Mà về cơ bản có nghĩa là bạn chỉ được phép xác định một đối tượng tại hầu hết các lần. (Các mệnh đề khác cho phép bạn không xác định một đối tượng bên ngoài ở tất cả nếu nó không bao giờ được sử dụng bất cứ nơi nào trong chương trình.)

Lưu ý rằng bạn có hai định nghĩa bên ngoài cho b. Một là cấu trúc mà bạn khởi tạo trong foo.c, và người kia là định nghĩa dự kiến ​​ trong main.c, (C11 6.9.2 p1-2):

Nếu việc kê khai của một định danh cho một đối tượng có phạm vi tập tin và bộ khởi tạo, khai báo là định nghĩa bên ngoài cho số nhận dạng.

Tuyên bố số nhận dạng cho đối tượng có phạm vi tệp không có bộ khởi tạo và không có trình chỉ định lớp lưu trữ hoặc với thông số lớp lưu trữ static, cấu thành định nghĩa dự kiến ​​. Nếu đơn vị dịch có chứa một hoặc nhiều định nghĩa dự kiến ​​cho số nhận dạng và đơn vị dịch không chứa định nghĩa bên ngoài cho số nhận dạng đó, thì hành vi chính xác như đơn vị dịch có chứa khai báo phạm vi tệp của số nhận dạng đó, với loại kết hợp ở cuối đơn vị dịch, với bộ khởi tạo bằng 0.

Vì vậy, bạn có nhiều định nghĩa là b. Tuy nhiên, có một lỗi khác, trong đó bạn đã xác định b với các loại khác nhau. Đầu tiên lưu ý rằng nhiều khai báo cho cùng một đối tượng có liên kết bên ngoài được cho phép. Tuy nhiên, khi cùng một tên được sử dụng trong hai tệp nguồn khác nhau, tên đó đề cập đến cùng một đối tượng (C11 6.2.2 p2):

Trong bộ đơn vị dịch và thư viện cấu thành toàn bộ chương trình, tuyên bố của một số nhận dạng cụ thể có liên kết bên ngoài biểu thị cùng một đối tượng hoặc hàm .

C đặt một giới hạn nghiêm ngặt về tờ khai đến cùng một đối tượng (C11 6.2.7 p2):

Tất cả các khai báo tham khảo các đối tượng hoặc chức năng tương tự sẽ có loại tương thích; nếu không, hành vi không xác định.

Vì các loại cho b trong mỗi tệp nguồn của bạn không thực sự khớp, hành vi không xác định. (Cái gì tạo nên một loại tương thích được mô tả chi tiết trong tất cả các C11 6.2.7, nhưng nó cơ bản để nắm được rằng các loại phải phù hợp.)

Vì vậy, bạn có hai thất bại cho b:

  • Nhiều định nghĩa.
  • Nhiều khai báo có loại không tương thích.

Về mặt kỹ thuật, việc khai báo int a trong cả hai tệp nguồn của bạn cũng vi phạm "một quy tắc định nghĩa". Lưu ý rằng a có liên kết bên ngoài (C11 6.2.2 p5):

Nếu khai báo định danh cho đối tượng có phạm vi tệp và không có bộ định danh lớp lưu trữ, liên kết của nó là bên ngoài.

Nhưng, từ báo giá từ C11 6.9.2 trước đó, những định nghĩa dự kiến ​​là định nghĩa bên ngoài và bạn chỉ được phép một trong số đó từ báo giá từ C11 6.9 ở trên cùng.

Tuyên bố từ chối trách nhiệm thông thường áp dụng cho hành vi không xác định. Bất cứ điều gì có thể xảy ra, và điều đó sẽ bao gồm hành vi bạn quan sát được.


Một phần mở rộng phổ biến để C là cho phép nhiều định nghĩa bên ngoài, và được mô tả trong tiêu chuẩn C trong Phụ lục J.5 thông tin (C11 J.5.11):

Có thể có nhiều so với định nghĩa bên ngoài cho số nhận dạng của đối tượng, với hoặc mà không sử dụng rõ ràng từ khóa extern; nếu các định nghĩa không đồng ý hoặc nhiều hơn một được khởi tạo, hành vi không được xác định (6.9.2).

(Nhấn mạnh là của tôi.) Vì định nghĩa cho a đồng ý, không có hại ở đó, nhưng định nghĩa cho b không đồng ý. Phần mở rộng này giải thích tại sao trình biên dịch của bạn không phàn nàn về sự hiện diện của nhiều định nghĩa. Từ trích dẫn của C11 6.2.2, trình liên kết sẽ cố gắng điều chỉnh nhiều tham chiếu đến cùng một đối tượng.

Trình liên kết thường sử dụng một trong hai mô hình để điều chỉnh nhiều định nghĩa của cùng một biểu tượng trong nhiều đơn vị dịch. Đây là "Mô hình chung" và "Mô hình Ref/Def". Trong "Mô hình chung", nhiều đối tượng có cùng tên được xếp thành một đối tượng duy nhất theo kiểu cách union để đối tượng có kích thước của định nghĩa lớn nhất. Trong "Mô hình Ref/Def", mỗi tên bên ngoài phải có chính xác một định nghĩa. GNU toolchain sử dụng "Common Model" theo mặc định, và "Relaxed Ref/Def Model", trong đó nó thực thi một quy tắc định nghĩa đúng cho một đơn vị dịch, nhưng không phàn nàn về vi phạm trên nhiều đơn vị dịch thuật .

"Mô hình chung" có thể bị chặn trong trình biên dịch GNU bằng cách sử dụng tùy chọn -fno-common.Khi tôi thử nghiệm này trên hệ thống của tôi, nó gây ra "Strict Ref/Def Model" hành vi mã tương tự như của bạn:

$ cat a.c 
#include <stdio.h> 
int a; 
struct { char a; int b; } b = { 2, 4 }; 
void foo() { printf("%zu\n", sizeof(b)); } 
$ cat b.c 
#include <stdio.h> 
extern void foo(); 
int a, b; 
int main() { printf("%zu\n", sizeof(b)); foo(); } 
$ gcc -fno-common a.c b.c 
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a' 
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here 
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b' 
/tmp/ccMoQ72v.o:(.data+0x0): first defined here 
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o 
collect2: ld returned 1 exit status 
$ 

Cá nhân tôi cảm nhận được cảnh báo cuối cùng do mối liên kết nên luôn luôn được cung cấp bất kể độ phân giải mô hình cho nhiều định nghĩa đối tượng, nhưng đó không phải là ở đây cũng không có.


Tài liệu tham khảo:
Unfortunately, I can't give you the link to my copy of the C11 Standard
What are extern variables in C?
The "Beginner's Guide to Linkers"
SAS Documentation on External Variable Models

+1

Điều đáng chú ý là hai cách triển khai điển hình là những cái thường được gọi là "mô hình def/ref" và "mô hình chung". Sau đó là những gì các trình liên kết Unix/Linux điển hình sử dụng và làm phát sinh hành vi được quan sát. Cụm từ "mô hình chung" xuất hiện để chỉ các khối Fortran COMMON như được triển khai trên nhiều khung hình chính trong thập niên 1960. – torek

+0

@torek: Cảm ơn bạn đã nhắc tôi nghiên cứu thêm một chút. Tôi đã cập nhật câu trả lời. – jxh

+0

Naive có lẽ, nhưng chính xác thì đơn vị dịch là gì? – NickHalden

0

Đồng thời foo đang được biên soạn, các b đó là trong phạm vi là hai ints vector {2, 4} hoặc 8 byte khi một sizeof (int) là 4. Khi chính được biên dịch, b vừa được redeclared như một int vì vậy kích thước của 4 có ý nghĩa. Cũng có thể có "padding bytes" được thêm vào cấu trúc sau "a" sao cho khe tiếp theo (int) được căn chỉnh trên ranh giới 4 byte.

-1

a và b có cùng địa chỉ vì chúng xuất hiện tại cùng một điểm trong tệp. Thực tế là b là một kích thước khác nhau không quan trọng nơi biến bắt đầu. Nếu bạn thêm một biến c giữa a và b vào một trong các tệp, địa chỉ của các b sẽ khác nhau.

1

b có cùng địa chỉ vì liên kết quyết định giải quyết xung đột cho bạn.

sizeof hiển thị các giá trị khác nhau vì sizeof được đánh giá tại thời gian biên dịch. Ở giai đoạn này, trình biên dịch chỉ biết về một b (được xác định trong tệp hiện tại).

+1

+1 vì biết đó là trình liên kết đặt 'b' vào cùng một vị trí bộ nhớ. – jxh

2

Đoạn mã dường như vi phạm quy tắc một định nghĩa. Nó sẽ gọi hành vi không xác định, không làm điều đó.

Giới thiệu về biến toàn cục a: không đặt định nghĩa của biến toàn cầu trong tệp tiêu đề vì nó sẽ được bao gồm trong nhiều tệp .c và dẫn đến nhiều định nghĩa. Chỉ cần đặt các khai báo trong tiêu đề và đặt định nghĩa vào một trong các tệp .c.

Trong t.h:

extern int a; 

Trong foo.c

int a; 

Giới thiệu về biến toàn cầu b: không xác định nó nhiều lần, sử dụng static để hạn chế biến trong một tập tin.

Trong foo.c:

static struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

Trong main.c

static int b; 
+0

+1 cho các biện pháp khắc phục cụ thể để tránh vi phạm quy tắc định nghĩa. – jxh

3

Chính thức, nó là bất hợp pháp để xác định các biến tương tự (hoặc chức năng) với liên kết bên ngoài nhiều hơn một lần. Vì vậy, từ quan điểm chính thức, hành vi của chương trình của bạn là không xác định.

Thực tế, cho phép nhiều định nghĩa của cùng biến với liên kết bên ngoài là phần mở rộng trình biên dịch phổ biến (phần mở rộng chung, được đề cập như vậy trong đặc tả ngôn ngữ). Tuy nhiên, để được sử dụng đúng cách, mỗi định nghĩa phải khai báo cùng loại đó. Và không có nhiều hơn một định nghĩa sẽ bao gồm initializer.

Trường hợp của bạn không khớp với mô tả tiện ích phổ biến. Mã của bạn biên dịch như là một tác dụng phụ của phần mở rộng phổ biến đó, nhưng hành vi của nó vẫn chưa được xác định.

+0

Tôi đã bỏ phiếu bình chọn câu trả lời này trước đó, nhưng câu trả lời này trước tiên đã giải quyết cụ thể phần mở rộng chung cho phép nhiều định nghĩa bên ngoài. – jxh

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