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
Câu hỏi của bạn là gì? –
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
Nếu bạn muốn giải quyết xung đột, hãy khai báo biến tĩnh. – snf