TL; DR: Tôi không nghĩ rằng hành vi của int a[5]={a[2]=1};
được xác định rõ ràng, ít nhất là trong C99.
Phần buồn cười là phần duy nhất có ý nghĩa với tôi là phần bạn đang hỏi: a[0]
được đặt thành 1
bởi vì toán tử gán trả về giá trị đã được gán. Mọi thứ khác đều không rõ ràng.
Nếu có mã int a[5] = { [2] = 1 }
, mọi thứ sẽ dễ dàng: Đó là cài đặt trình khởi tạo được chỉ định a[2]
cho 1
và mọi thứ khác 0
. Nhưng với việc { a[2] = 1 }
chúng ta có một trình khởi tạo không được chỉ định có chứa một biểu thức gán, và chúng ta rơi xuống một lỗ hổng.
Đây là những gì tôi đã tìm thấy cho đến nay:
a
phải là một biến cục bộ.
6.7.8 Khởi tạo
- Tất cả các biểu thức trong bộ khởi tạo cho một đối tượng có thời lượng lưu trữ tĩnh phải là các biểu thức hằng số hoặc các ký tự chuỗi.
a[2] = 1
không phải là một biểu thức hằng, vì vậy a
phải có lưu trữ tự động.
a
nằm trong phạm vi khởi tạo của chính nó.
6.2.1 Phạm vi nhận dạng
- Các thẻ cấu trúc, liên hợp và liệt kê có phạm vi bắt đầu ngay sau sự xuất hiện của thẻ trong bộ chỉ định kiểu khai báo thẻ. Mỗi hằng số liệt kê có phạm vi bắt đầu ngay sau khi xuất hiện điều tra viên xác định của nó trong danh sách điều tra viên. Bất kỳ mã định danh nào khác có phạm vi bắt đầu ngay sau khi hoàn thành bộ khai báo của nó.
Bộ khai báo là a[5]
, vì vậy các biến nằm trong phạm vi khởi tạo của chính chúng.
a
vẫn tồn tại trong quá trình khởi tạo của chính nó.
6.2.4 Thời lượng lưu trữ của các đối tượng
Một đối tượng có nhận dạng được khai báo không có mối liên hệ và không có sự xác định lưu trữ-lớp static
có thời gian lưu trữ tự động .
Đối với một đối tượng như vậy không có kiểu mảng có độ dài thay đổi, thời gian tồn tại của nó kéo dài từ khi nhập vào khối mà nó được liên kết cho đến khi việc thực thi khối đó kết thúc theo bất kỳ cách nào. (Việc nhập một khối kèm theo hoặc gọi một hàm sẽ tạm dừng, nhưng không kết thúc, việc thực thi khối hiện tại.) Nếu khối được nhập đệ quy, một thể hiện mới của đối tượng sẽ được tạo mỗi lần. Giá trị ban đầu của đối tượng là không xác định. Nếu một khởi tạo được chỉ định cho đối tượng, nó được thực hiện mỗi khi đạt được khai báo trong quá trình thực thi khối; nếu không, giá trị trở nên không xác định mỗi khi đạt đến khai báo.
Có một điểm trình tự sau a[2]=1
.
6.8 Các câu lệnh và khối
- Một biểu thức đầy đủ là một biểu thức không phải là một phần của một biểu thức khác hoặc của một bộ khai báo. Mỗi điều sau đây là một biểu thức đầy đủ: một trình khởi tạo ; biểu thức trong một câu lệnh biểu thức; biểu thức điều khiển của một câu lệnh lựa chọn (
if
hoặc switch
); biểu thức điều khiển của một while
hoặc do
câu lệnh; mỗi biểu thức (tùy chọn) của một for
câu lệnh; biểu thức (tùy chọn) trong một return
câu lệnh. Phần cuối của một biểu thức đầy đủ là một điểm trình tự.
Lưu ý rằng ví dụ như trong int foo[] = { 1, 2, 3 }
các { 1, 2, 3 }
phần là danh sách cú đúp kín của initializers, mỗi trong số đó có một điểm chuỗi sau nó.
Khởi tạo được thực hiện theo thứ tự danh sách trình khởi tạo.
6.7.8 Khởi tạo
- Mỗi danh sách bộ khởi tạo có dấu ngoặc nhọn có một đối tượng hiện tại được liên kết . Khi không có chỉ định nào, các subobject của đối tượng hiện tại được khởi tạo theo thứ tự theo kiểu của đối tượng hiện tại: phần tử mảng theo thứ tự chỉ số con tăng dần, thành viên cấu trúc theo thứ tự khai báo và thành viên được đặt tên đầu tiên của một liên hợp. [...]
- Việc khởi tạo sẽ diễn ra theo thứ tự danh sách trình khởi tạo, mỗi trình khởi tạo được cung cấp cho một subobject cụ thể sẽ ghi đè bất kỳ trình khởi tạo nào được liệt kê trước đó cho cùng một subobject; tất cả các subobject không được khởi tạo rõ ràng sẽ được khởi tạo ngầm giống như các đối tượng có thời lượng lưu trữ tĩnh.
Tuy nhiên, các biểu thức khởi tạo không nhất thiết phải được đánh giá theo thứ tự.
6.7.8 Khởi tạo
- Thứ tự mà bất kỳ tác dụng phụ nào xảy ra giữa các biểu thức danh sách khởi tạo là không xác định.
Tuy nhiên, điều đó vẫn để lại một số câu hỏi chưa được giải đáp:
Các điểm trình tự có liên quan không? Quy tắc cơ bản là:
6.5 Biểu thức
- Giữa điểm trình tự trước và điểm tiếp theo, một đối tượng sẽ có giá trị được lưu trữ của nó được sửa đổi nhiều nhất một lần bằng cách đánh giá một biểu thức . Hơn nữa, giá trị trước sẽ chỉ được đọc để xác định giá trị được lưu trữ.
a[2] = 1
là một biểu thức, nhưng khởi tạo thì không.
Điều này hơi mâu thuẫn với Phụ lục J:
J.2 Hành vi không xác định
- Giữa hai điểm trình tự, một đối tượng được sửa đổi nhiều lần, hoặc được sửa đổi và giá trị trước đó được đọc ngoài để xác định giá trị được lưu trữ (6.5).
Phụ lục J cho biết bất kỳ sửa đổi nào cũng được tính, không chỉ sửa đổi theo biểu thức. Nhưng cho rằng các phụ lục không mang tính quy chuẩn, chúng ta có thể bỏ qua điều đó.
Các quá trình khởi tạo subobject được sắp xếp như thế nào đối với các biểu thức khởi tạo? Có phải tất cả các trình khởi tạo đều được đánh giá trước (theo một số thứ tự), sau đó các subobject được khởi tạo với kết quả (theo thứ tự danh sách trình khởi tạo)? Hoặc chúng có thể được xen kẽ?
Tôi nghĩ int a[5] = { a[2] = 1 }
được thực hiện như sau:
- Bộ nhớ cho
a
được cấp phát khi khối chứa của nó được nhập. Nội dung là không xác định tại thời điểm này.
- Bộ khởi tạo (duy nhất) được thực thi (
a[2] = 1
), theo sau là một điểm trình tự. Này lưu trữ 1
trong a[2]
và lợi nhuận 1
.
- Điều đó
1
được sử dụng để khởi tạo a[0]
(trình khởi tạo đầu tiên khởi tạo subobject đầu tiên).
Nhưng ở đây mọi thứ trở nên mờ vì các yếu tố còn lại ( a[1]
, a[2]
, a[3]
, a[4]
) có nghĩa vụ phải được khởi tạo 0
, nhưng nó không rõ ràng khi: Liệu nó xảy ra trước khi a[2] = 1
được đánh giá? Nếu vậy, a[2] = 1
sẽ "thắng" và ghi đè a[2]
, nhưng liệu phép gán đó có hành vi không xác định vì không có điểm trình tự nào giữa khởi tạo 0 và biểu thức gán không? Các điểm trình tự thậm chí có liên quan không (xem ở trên)? Hoặc không khởi tạo xảy ra sau khi tất cả các trình khởi tạo được đánh giá? Nếu vậy, a[2]
cuối cùng sẽ là 0
.
Bởi vì tiêu chuẩn C không xác định rõ ràng những gì xảy ra ở đây, tôi tin rằng hành vi là không xác định (do bỏ sót).
a[2]=1
đánh giá là1
.