{0} có nghĩa là gì khi khởi tạo một đối tượng?


252

Khi {0}được sử dụng để khởi tạo một đối tượng, nó có nghĩa là gì? Tôi không thể tìm thấy bất kỳ tài liệu tham khảo {0}nào ở bất cứ đâu và vì các dấu ngoặc nhọn, các tìm kiếm của Google không hữu ích.

Mã ví dụ:

SHELLEXECUTEINFO sexi = {0}; // what does this do?
sexi.cbSize = sizeof(SHELLEXECUTEINFO);
sexi.hwnd = NULL;
sexi.fMask = SEE_MASK_NOCLOSEPROCESS;
sexi.lpFile = lpFile.c_str();
sexi.lpParameters = args;
sexi.nShow = nShow;

if(ShellExecuteEx(&sexi))
{
    DWORD wait = WaitForSingleObject(sexi.hProcess, INFINITE);
    if(wait == WAIT_OBJECT_0)
        GetExitCodeProcess(sexi.hProcess, &returnCode);
}

Không có nó, đoạn mã trên sẽ bị sập khi chạy.

Câu trả lời:


302

Những gì xảy ra ở đây được gọi là khởi tạo tổng hợp . Dưới đây là định nghĩa (viết tắt) của tổng hợp từ phần 8.5.1 của thông số ISO:

Tập hợp là một mảng hoặc một lớp không có hàm tạo do người dùng khai báo, không có thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ, không có lớp cơ sở và không có hàm ảo.

Bây giờ, sử dụng {0}để khởi tạo một tổng hợp như thế này về cơ bản là một mẹo cho 0toàn bộ. Điều này là do khi sử dụng khởi tạo tổng hợp, bạn không phải chỉ định tất cả các thành viên và thông số kỹ thuật yêu cầu tất cả các thành viên không xác định được khởi tạo mặc định, có nghĩa là được đặt thành 0các loại đơn giản.

Dưới đây là trích dẫn có liên quan từ thông số kỹ thuật:

Nếu có ít bộ khởi tạo trong danh sách hơn số thành viên trong tổng hợp, thì mỗi thành viên không được khởi tạo rõ ràng sẽ được khởi tạo mặc định. Thí dụ:

struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };

khởi tạo ss.avới 1, ss.bvới "asdf"ss.cvới giá trị của biểu thức của biểu mẫu int(), nghĩa là , 0.

Bạn có thể tìm thấy thông số kỹ thuật đầy đủ về chủ đề này ở đây


15
Phản ứng tuyệt vời. Chỉ muốn thêm rằng việc khởi tạo một tập hợp với {0} cũng giống như khởi tạo nó chỉ bằng {}. Có lẽ trước đây làm cho rõ ràng hơn rằng các loại tích hợp trở thành số không.
James Hopkin

7
Một số trình biên dịch bị sặc trên {}, đó là lý do tại sao {0} được sử dụng
Branan

10
Không hoàn toàn .. Trong C ++, nếu thành viên đầu tiên không thể được xây dựng bằng 0 thì {0} sẽ không hoạt động. Vd: struct A {B b; int i; char c; }; cấu trúc B {B (); B (chuỗi); }; A a = {}; // câu lệnh này không thể được viết lại thành 'A a = {0}'.
Aaron

25
@Branan, đó là vì trong C "{}" không hợp lệ. Trong C ++ thì có. @ don.neufeld, điều này đã thay đổi với C ++ 03 (khởi tạo mặc định -> khởi tạo giá trị). Các trích dẫn trích dẫn tiêu chuẩn C ++ 98, hãy nhớ.
Julian Schaub - litb

3
Vì vậy, điều này có thay thế nhu cầu sử dụng ZeroMemory () (trong VC ++) không?
Ray

89

Một điều cần lưu ý là kỹ thuật này sẽ không đặt các byte đệm thành 0. Ví dụ:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Không giống như:

foo a;
memset(&a,0,sizeof(a));

Trong trường hợp đầu tiên, các byte pad giữa c và i không được khởi tạo. Tại sao bạn quan tâm? Chà, nếu bạn đang lưu dữ liệu này vào đĩa hoặc gửi nó qua mạng hoặc bất cứ điều gì, bạn có thể gặp vấn đề về bảo mật.


13
Tất nhiên, đó chỉ là vấn đề bảo mật nếu bạn viết (f, & a, sizeof (a)) ', có thể tạo mã hóa tệp khác nhau trên các bộ xử lý / trình biên dịch khác nhau. Đầu ra được định dạng tốt sẽ an toàn mà không cần bộ nhớ.
Aaron

3
Ngoài ra, nếu bạn đang gửi nội dung qua mạng, bạn sẽ luôn đặt căn chỉnh được đóng gói. Bằng cách đó bạn sẽ có được càng ít byte đệm càng tốt.
Đánh dấu Kegel

18
Cần lưu ý rằng mặc dù thông số kỹ thuật không yêu cầu phần đệm được khởi tạo, bất kỳ trình biên dịch lành mạnh nào cũng sẽ làm được, vì nó chỉ tốn thời gian để khởi tạo "xung quanh" nó.
Tomas

4
Tôi muốn có vấn đề với việc sử dụng các từ "không". Byte đệm được thực hiện được xác định. Trình biên dịch có thể tự do biến foo a = {0} thành bộ nhớ (& a, 0, sizeof (a)) tùy ý. Không bắt buộc phải "bỏ qua" các byte đệm và chỉ đặt foo.c và foo.i. +1 cho lỗi bảo mật (tiềm năng) tho
SecurityMatt

3
@Leushenko điểm 19 nói rằng "tất cả các tiểu dự án không được khởi tạo một cách rõ ràng", vì vậy tôi nghiêng về điểm 21 (mà bạn đã trích dẫn) là cẩu thả. Sẽ thật kỳ quái nếu thông số kỹ thuật cho phép phần đệm không được khởi tạo cho đến thời điểm khởi tạo cuối cùng, sau đó phần đệm phải bằng 0, đặc biệt là xem xét các trình khởi tạo có thể xuất hiện không theo thứ tự khi sử dụng bộ khởi tạo được chỉ định.
MM

20

Lưu ý rằng trình khởi tạo tổng hợp trống cũng hoạt động:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};

11

Trả lời tại sao ShellExecuteEx() bị sập: SHELLEXECUTEINFOcấu trúc "sexi" của bạn có nhiều thành viên và bạn chỉ khởi tạo một số trong số họ.

Ví dụ, thành viên sexi.lpDirectorycó thể chỉ ở bất cứ đâu, nhưngShellExecuteEx() vẫn sẽ cố gắng sử dụng nó, do đó bạn sẽ bị vi phạm quyền truy cập bộ nhớ.

Khi bạn bao gồm dòng:

SHELLEXECUTEINFO sexi = {0};

trước phần còn lại của thiết lập cấu trúc, bạn đang yêu cầu trình biên dịch loại bỏ tất cả các thành viên cấu trúc trước khi bạn khởi tạo các cấu trúc cụ thể mà bạn quan tâm. ShellExecuteEx()Biết rằng nếu sexi.lpDirectorybằng 0, thì nên bỏ qua nó.


7

Tôi cũng sử dụng nó để khởi tạo chuỗi, ví dụ.

char mytext[100] = {0};

5
Mặc dù, tất nhiên, nếu mytext đang được sử dụng một chuỗi, char mytext [100]; mytext [0] = '\ 0'; sẽ có tác dụng tương tự khi đưa ra một chuỗi rỗng, nhưng chỉ khiến cho việc thực hiện bằng 0 byte đầu tiên.
Chris Young

@Chris: Tôi thường ước có một cú pháp để khởi tạo một phần đối tượng. Có thể "khai báo và khởi tạo" x, sau đó thực hiện tương tự với y, sau đó z, sẽ tốt hơn rất nhiều so với việc phải khai báo x, y và z, sau đó khởi tạo x, y và z, nhưng chỉ khởi tạo 100 byte khi chỉ một thực sự cần khởi tạo có vẻ khá lãng phí.
supercat

7

{0}là một trình khởi tạo hợp lệ cho bất kỳ loại (đối tượng hoàn chỉnh) nào, trong cả C và C ++. Đây là một thành ngữ phổ biến được sử dụng để khởi tạo một đối tượng về 0 (đọc tiếp để xem điều đó có nghĩa là gì).

Đối với các loại vô hướng (loại số học và con trỏ), các dấu ngoặc là không cần thiết, nhưng chúng được cho phép rõ ràng. Trích dẫn dự thảo N1570 của tiêu chuẩn ISO C, mục 6.7.9:

Bộ khởi tạo cho một vô hướng sẽ là một biểu thức, được đặt tùy ý trong dấu ngoặc nhọn.

Nó khởi tạo đối tượng về 0 ( 0đối với số nguyên, 0.0đối với dấu phẩy động, con trỏ null cho con trỏ).

Đối với các loại không vô hướng (cấu trúc, mảng, công đoàn), {0} chỉ định rằng phần tử đầu tiên của đối tượng được khởi tạo thành không. Đối với các cấu trúc có chứa cấu trúc, mảng cấu trúc, v.v., điều này được áp dụng đệ quy, do đó phần tử vô hướng đầu tiên được đặt thành 0, phù hợp với loại. Như trong bất kỳ trình khởi tạo nào, mọi phần tử không được chỉ định đều được đặt thành không.

Niềng răng trung gian ({ , }) có thể được bỏ qua; ví dụ cả hai đều hợp lệ và tương đương:

int arr[2][2] = { { 1, 2 }, {3, 4} };

int arr[2][2] = { 1, 2, 3, 4 };

đó là lý do tại sao bạn không phải viết, ví dụ, { { 0 } } đối với loại có phần tử đầu tiên không phải là vô hướng.

Vậy đây:

some_type obj = { 0 };

là một cách tốc ký để khởi tạo objvề 0, nghĩa là mỗi đối tượng con vô hướng của objđược đặt thành 0nếu nó là một số nguyên, 0.0nếu đó là dấu phẩy động hoặc con trỏ rỗng nếu đó là một con trỏ.

Các quy tắc tương tự cho C ++.

Trong trường hợp cụ thể của bạn, vì bạn đang gán các giá trị cho sexi.cbSizevà cứ thế, rõ ràng đó SHELLEXECUTEINFOlà kiểu cấu trúc hoặc lớp (hoặc có thể là liên kết, nhưng có lẽ là không), vì vậy không phải tất cả điều này đều áp dụng, nhưng như tôi đã nói { 0 }là phổ biến thành ngữ có thể được sử dụng trong các tình huống chung hơn.

Điều này không (nhất thiết) tương đương với việc sử dụng memsetđể đặt biểu diễn của đối tượng thành all-bits-zero. Cả dấu phẩy động 0.0và con trỏ rỗng đều không nhất thiết phải được biểu diễn dưới dạng all-bits-zero và bộ { 0 }khởi tạo không nhất thiết phải đặt các byte đệm cho bất kỳ giá trị cụ thể nào. Tuy nhiên, trên hầu hết các hệ thống, nó có thể có tác dụng tương tự.


1
Trong C ++, {0}không phải là trình khởi tạo hợp lệ cho một đối tượng không có hàm tạo chấp nhận 0; cũng không phải cho một tổng hợp có phần tử đầu tiên là như vậy (hoặc một tổng hợp không có phần tử nào)
MM

3

Đã được một thời gian kể từ khi tôi làm việc trong c / c ++ nhưng IIRC, cùng một phím tắt cũng có thể được sử dụng cho các mảng.


2

Tôi đã luôn tự hỏi, tại sao bạn nên sử dụng một cái gì đó như

struct foo bar = { 0 };

Đây là một trường hợp thử nghiệm để giải thích:

kiểm tra

struct f {
    int x;
    char a;
} my_zero_struct;

int main(void)
{
    return my_zero_struct.x;
}

Tôi biên dịch với gcc -O2 -o check check.cvà sau đó xuất bảng ký hiệu với readelf -s check | sort -k 2(đây là với gcc 4.6.3 trên ubfox 12.04.2 trên hệ thống x64). Trích đoạn:

59: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
48: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
25: 0000000000601018     0 SECTION LOCAL  DEFAULT   25 
33: 0000000000601018     1 OBJECT  LOCAL  DEFAULT   25 completed.6531
34: 0000000000601020     8 OBJECT  LOCAL  DEFAULT   25 dtor_idx.6533
62: 0000000000601028     8 OBJECT  GLOBAL DEFAULT   25 my_zero_struct
57: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT  ABS _end

Phần quan trọng ở đây là, đó my_zero_structlà sau __bss_start. Phần ".bss" trong chương trình C là phần bộ nhớ được đặt thành 0 trước khi main được gọi là xem wikipedia trên .bss .

Nếu bạn thay đổi mã ở trên thành:

} my_zero_struct = { 0 };

Sau đó, kết quả thực thi "kiểm tra" trông giống hệt ít nhất với trình biên dịch gcc 4.6.3 trên ubfox 12.04.2; những my_zero_structvẫn còn trong.bss phần và do đó nó sẽ tự động khởi tạo không, trước khimain được gọi.

Gợi ý trong các ý kiến, rằng một memsetcấu trúc có thể khởi tạo cấu trúc "đầy đủ" cũng không phải là một sự cải tiến, bởi vì.bss phần này bị xóa hoàn toàn, điều đó cũng có nghĩa là cấu trúc "đầy đủ" được đặt thành không.

thể tiêu chuẩn ngôn ngữ C không đề cập đến bất kỳ điều gì trong số này, nhưng trong trình biên dịch C trong thế giới thực, tôi chưa bao giờ thấy một hành vi nào khác.


Các biến toàn cục và tĩnh luôn được khởi tạo theo mặc định là 0 hoặc ctor mặc định. Nhưng nếu bạn khai báo thể hiện của f cục bộ, bạn có thể nhận được các kết quả khác nhau.
Logman

0

Đó là đường tổng hợp để khởi tạo toàn bộ cấu trúc của bạn thành các giá trị rỗng / zero / null.

Phiên bản dài

SHELLEXECUTEINFO sexi;
sexi.cbSize = 0;
sexi.fMask = 0;
sexi.hwnd = NULL;
sexi.lpVerb = NULL;
sexi.lpFile = NULL;
sexi.lpParameters = NULL;
sexi.lpDirectory = NULL;
sexi.nShow = nShow;
sexi.hInstApp = 0;
sexi.lpIDList = NULL;
sexi.lpClass = NULL;
sexi.hkeyClass = 0;
sexi.dwHotKey = 0;
sexi.hMonitor = 0;
sexi.hProcess = 0;

Phiên bản ngắn

SHELLEXECUTEINFO sexi = {0};

Điều đó có dễ hơn nhiều không?

Nó cũng tốt bởi vì:

  • bạn không cần phải săn lùng mọi thành viên và khởi tạo nó
  • bạn không phải lo lắng rằng bạn có thể không khởi tạo thành viên mới khi họ được thêm vào sau
  • bạn không phải gọiZeroMemory

-5

{0} là một mảng ẩn danh chứa phần tử của nó là 0.

Điều này được sử dụng để khởi tạo một hoặc tất cả các phần tử của mảng bằng 0.

ví dụ: int mảng [8] = {0};

Trong trường hợp này, tất cả các phần tử của mảng sẽ được khởi tạo là 0.


4
{0}không phải là một mảng ẩn danh. Nó thậm chí không phải là một biểu thức. Đó là một trình khởi tạo.
Keith Thompson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.