2009-11-20 74 views
5

Ở mọi nơi tôi nhìn có những người tranh luận rằng các biến chưa được khởi tạo là xấu và tôi chắc chắn đồng ý và hiểu tại sao - tuy nhiên; câu hỏi của tôi là, có những dịp khi bạn không muốn làm điều này?Khởi tạo các mảng trong C++

Ví dụ, lấy mã:

char arrBuffer[1024] = { '\0' }; 

Liệu nulling toàn bộ mảng tạo ra một tác động hiệu quả hơn sử dụng mảng mà không initialising nó?

+1

Ví dụ của bạn không làm rỗng toàn bộ mảng, chỉ là phần tử đầu tiên ... –

+13

Thực ra, nó * không * bỏ toàn bộ mảng. – paxdiablo

+5

Ví dụ khởi tạo phần tử đầu tiên của mảng với một '\ 0' cụ thể và tất cả các phần tử khác có mặc định '\ 0'. Mã 'char a [4] = {'A'};' đặt 'A' trong một [0], '\ 0' trong [1] ... – pmg

Trả lời

10

tôi giả sử một chồng khởi vì mảng tĩnh được tự động khởi tạo.
G ++ đầu ra

char whatever[2567] = {'\0'}; 
    8048530:  8d 95 f5 f5 ff ff  lea -0xa0b(%ebp),%edx 
    8048536:  b8 07 0a 00 00   mov $0xa07,%eax 
    804853b:  89 44 24 08    mov %eax,0x8(%esp) 
    804853f:  c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 
    8048546:  00 
    8048547:  89 14 24    mov %edx,(%esp) 
    804854a:  e8 b9 fe ff ff   call 8048408 <[email protected]> 

Vì vậy, bạn khởi tạo với { '\ 0'} và một cuộc gọi đến memset được thực hiện, vì vậy có, bạn có một buổi biểu diễn hit.

+0

(+1), nhưng nếu mảng không tĩnh thì sao? – Konrad

+0

Ví dụ này là chính xác cho mảng không tĩnh, mảng dựa trên ngăn xếp. Nếu với không tĩnh bạn có nghĩa là kích thước không rõ (như trong C99) trong thời gian biên dịch mã sẽ nhiều hơn hoặc ít hơn giống như memset luôn được gọi là null nó. –

-2

Và tại sao bạn quan tâm đến lợi ích hiệu suất, bạn sẽ nhận được bao nhiêu hiệu suất bằng cách không khởi chạy nó, và nó có nhiều hơn thời gian được lưu trong quá trình gỡ lỗi do con trỏ rác hay không.

+3

Không phải là câu hỏi tôi đang hỏi. Tôi đồng ý, an toàn là tối quan trọng, tuy nhiên tôi muốn biết liệu nó sẽ có tác động hiệu quả hay không. – Konrad

+0

Tôi đoán mọi người đều biết sẽ có tác động hiệu suất không đáng kể do bản sao bộ nhớ. –

4

Quy tắc là các biến phải được đặt trước khi chúng được sử dụng.

Bạn làm không phải khởi tạo rõ ràng chúng khi tạo nếu bạn biết bạn sẽ đặt chúng ở nơi khác trước khi sử dụng.

Ví dụ, đoạn mã sau là hoàn toàn okay:

int main (void) { 
    int a[1000]; 
    : : 
    for (int i =0; i < sizeof(a)/sizeof(*a); i++) 
     a[i] = i; 
    : : 
    // Now use a[whatever] here. 
    : : 
    return 0; 
} 

Trong trường hợp đó, nó là lãng phí để khởi tạo mảng tại thời điểm thành lập.

Để xem liệu có một hình phạt hiệu suất hay không, nó phụ thuộc một phần vào nơi biến của bạn được xác định và một phần trong môi trường thực thi. Tiêu chuẩn C đảm bảo rằng các biến được xác định với thời gian lưu trữ tĩnh (ở cấp độ tệp hoặc là số liệu trong hàm) được khởi tạo lần đầu tiên thành mẫu bit của tất cả các số không, sau đó được đặt thành giá trị được khởi tạo tương ứng.

không ủy quyền bước thứ hai được thực hiện như thế nào. Một cách điển hình là chỉ cần trình biên dịch tự tạo ra biến khởi tạo và đặt nó trong tệp thực thi để nó được khởi tạo bởi thực tế là tệp thực thi được nạp. Điều này sẽ không có tác động hiệu suất (để khởi tạo, rõ ràng nó sẽ có một số tác động cho tải chương trình).

Tất nhiên, việc triển khai có thể muốn tiết kiệm không gian trong tệp thực thi và khởi tạo các biến đó bằng mã (trước khi chính được gọi). Điều này sẽ có tác động hiệu suất nhưng có khả năng là nhỏ. Đối với những biến có thời gian lưu trữ tự động (biến địa phương và như vậy), chúng không bao giờ được khởi tạo ngầm trừ khi bạn gán thứ gì đó cho chúng, do đó cũng sẽ có một hình phạt về hiệu năng cho điều đó. Bởi "không bao giờ được khởi tạo ngầm", tôi có nghĩa là đoạn mã:

void x(void) { 
    int x[1000]; 
    ... 
} 

sẽ dẫn đến x [] có giá trị không xác định. Nhưng kể từ:

void x(void) { 
    int x[1000] = {0}; 
} 

đơn giản có thể dẫn đến hoạt động loại memcpy 1000 số nguyên (nhiều khả năng là ghi nhớ cho trường hợp đó), điều này cũng có thể nhanh. Bạn chỉ cần nhớ rằng việc khởi tạo sẽ xảy ra mỗi lần thời gian mà chức năng đó được gọi.

+0

Hmm, tôi chưa bao giờ đọc trong bản nháp Chuẩn về việc khởi tạo hai giai đoạn này. Tất cả những gì 6.7.8/10 nói "Nếu một đối tượng có thời gian lưu trữ tĩnh không được khởi tạo một cách rõ ràng, thì ... - nếu nó có kiểu con trỏ, nó được khởi tạo thành một con trỏ rỗng ...". Bạn có thể vui lòng gimme một số gợi ý mà tôi có thể tìm thấy nó nói rằng họ là tất cả-bit-zero khởi tạo đầu tiên? Cảm ơn bạn đời. –

+0

Thậm chí nếu nó đã nói rằng, tôi nghĩ việc thực hiện có thể "as-if" theo cách của nó ra khỏi nó, nếu nó muốn. –

+1

@JS, s5.1.2 Môi trường thực thi (c1x, n1362): Hai môi trường thực thi được định nghĩa: tự do và được lưu trữ. Trong cả hai trường hợp, khởi động chương trình xảy ra khi một hàm C được chỉ định được gọi bởi môi trường thực hiện. Trước khi khởi động chương trình, vùng lưu trữ chứa tất cả các đối tượng có thời gian lưu trữ tĩnh đầu tiên phải được xóa (tất cả các byte được đặt thành 0), thì các đối tượng sẽ được khởi tạo (được đặt thành giá trị ban đầu của chúng). Cách thức và thời gian khởi tạo như vậy không được chỉ định. Chương trình chấm dứt trả về kiểm soát môi trường thực thi. – paxdiablo

0

Đối với mảng lớn, tác động hiệu suất có thể đáng kể.Việc khởi tạo tất cả các biến theo mặc định thực sự không mang lại nhiều lợi ích. Nó không phải là một giải pháp cho mã xấu, hơn nữa nó có thể ẩn các vấn đề thực tế mà có thể bị bắt là trình biên dịch khác. Bạn cần phải theo dõi trạng thái của tất cả các biến trong toàn bộ tuổi thọ của chúng để làm cho mã của bạn đáng tin cậy.

2

Đo lường!

#include <stdio.h> 
#include <time.h> 

int main(void) { 
    clock_t t0; 
    int k; 

    t0 = clock(); 
    for (k=0; k<1000000; k++) { 
    int a[1000]; 
    a[420] = 420; 
    } 
    printf("Without init: %f secs\n", (double)(clock() - t0)/CLOCKS_PER_SEC); 

    t0 = clock(); 
    for (k=0; k<1000000; k++) { 
    int a[1000] = {0}; 
    a[420] = 420; 
    } 
    printf(" With init: %f secs\n", (double)(clock() - t0)/CLOCKS_PER_SEC); 

    return 0; 
} 
 
$ gcc measure.c 
$ ./a.out 
Without init: 0.000000 secs 
    With init: 0.280000 secs 
$ gcc -O2 measure.c 
$ ./a.out 
Without init: 0.000000 secs 
    With init: 0.000000 secs 
+0

Bạn đang đo khởi tạo biến trên ngăn xếp. Điều này khác với trình khởi tạo biến toàn cục. Mặc dù, nó không rõ ràng mà áp phích đang sử dụng. – spoulson

+0

Đồng thời khởi tạo '0' không phải '\ 0' ở đó. Không chắc chắn nếu điều đó sẽ tạo ra sự khác biệt .. – Konrad

+1

Điểm là, nếu áp phích lo lắng với vài nano giây, anh ta nên đo trước khi xác định nơi để cải thiện mã. Tôi chắc chắn rằng 99,9999% sẽ không khởi tạo được vấn đề. – pmg

7

Nếu biến là toàn cầu hoặc tĩnh, thì dữ liệu của nó thường được lưu trữ nguyên văn trong tệp thực thi được biên dịch. Vì vậy, char arrBuffer[1024] của bạn sẽ tăng kích thước thực thi lên 1024 byte. Khởi tạo nó sẽ đảm bảo tệp thực thi chứa dữ liệu của bạn thay vì 0 mặc định hoặc bất kỳ trình biên dịch nào chọn. Khi chương trình bắt đầu, không cần xử lý để khởi tạo các biến.

Mặt khác, các biến trên ngăn xếp, chẳng hạn như các biến chức năng cục bộ không tĩnh, không được lưu trữ trong tệp thực thi theo cùng một cách. Thay vào đó, về chức năng nhập không gian được phân bổ trên ngăn xếp và một memcpy đặt dữ liệu vào biến, do đó ảnh hưởng đến hiệu suất.

0

Để trả lời câu hỏi của bạn: nó có thể có tác động hiệu suất. Có thể là một trình biên dịch có thể phát hiện ra rằng các giá trị của mảng không được sử dụng và không làm chúng. Có thể.

Cá nhân tôi nghĩ đây là vấn đề về phong cách cá nhân. Tôi bị cám dỗ để nói: để nó chưa được khởi tạo, và sử dụng một công cụ giống như Lint để cho bạn biết nếu bạn đang sử dụng nó uninitialised, mà chắc chắn là một lỗi (như trái ngược với việc sử dụng giá trị mặc định và không được nói, đó cũng là một lỗi, nhưng một cái câm).

0

Tôi cho rằng đó là một lời khuyên xấu để yêu cầu tất cả các biến được khởi tạo mặc định tại thời điểm khai báo. Trong hầu hết các trường hợp, nó là không cần thiết và mang hình phạt hiệu suất.

Ví dụ, tôi thường sử dụng mã dưới đây để chuyển đổi một số thành một chuỗi:

char s[24]; 
sprintf(s, "%d", int_val); 

Tôi sẽ không viết:

char s[24] = "\0"; 
sprintf(s, "%d", int_val); 

trình biên dịch hiện đại có thể cho biết nếu một biến được sử dụng mà không được khởi tạo.

0

Biến của bạn phải được khởi tạo thành giá trị có ý nghĩa. Việc thiết lập một cách mù quáng và ngây thơ về mọi thứ bằng 0 không tốt hơn nhiều so với việc bỏ qua nó chưa được khởi tạo. Nó có thể làm cho sự cố mã không hợp lệ, thay vì hoạt động không thể đoán trước, nhưng nó sẽ không làm cho mã chính xác.

Nếu bạn chỉ ngây thơ ra khỏi mảng khi tạo nó chỉ để tránh các biến chưa được khởi tạo, thì vẫn là một cách hợp lý uninitialized. nó chưa có giá trị nào có ý nghĩa trong ứng dụng của bạn.

Nếu bạn định khởi tạo biến (và bạn nên), hãy cung cấp cho chúng các giá trị hợp lý trong ứng dụng của bạn. Phần còn lại của mã của bạn có mong đợi mảng không phải là ban đầu không? Nếu có, hãy đặt thành 0. Nếu không, hãy đặt nó thành một số giá trị có ý nghĩa khác.

Hoặc nếu phần còn lại của mã của bạn dự kiến ​​sẽ ghi vào mảng, mà không đọc trước tiên, thì bằng mọi cách hãy để nó không được khởi tạo.

0

Cá nhân tôi chống lại việc tạo mảng lúc tạo. Hãy xem xét hai đoạn mã sau đây.

char buffer[1024] = {0}; 
for (int i = 0; i < 1000000; ++i) 
{ 
    // Use buffer 
} 

vs

for (int i = 0; i < 1000000; ++i) 
{ 
    char buffer[1024] = {0}; 
    // Use buffer 
} 

Trong ví dụ đầu tiên tại sao bận tâm khởi đệm kể từ lần thứ hai xung quanh bộ đệm vòng lặp không còn 0 khởi tạo? Việc sử dụng bộ đệm của tôi phải hoạt động mà không có nó được khởi tạo cho tất cả trừ lần lặp đầu tiên. Tất cả các khởi tạo không được tiêu thụ thời gian, bloat mã và lỗi tối nghĩa nếu thông thường tôi chỉ đi qua vòng lặp một lần.

Mặc dù tôi có thể xác định lại mã là ví dụ thứ hai, tôi có thực sự muốn khởi tạo bộ đệm trong vòng lặp nếu tôi có thể viết lại mã của mình để không cần thiết không?

Tôi nghi ngờ hầu hết các trình biên dịch những ngày này có các tùy chọn để điền các biến chưa được khởi tạo với các giá trị không 0. Chúng tôi chạy tất cả các bản dựng lỗi của chúng tôi theo cách này để giúp phát hiện việc sử dụng các biến chưa được khởi tạo và trong chế độ phát hành, chúng tôi tắt tùy chọn để các biến thực sự chưa được khởi tạo. Như Sherwood Hu cho biết, một số trình biên dịch có thể tiêm mã để giúp phát hiện việc sử dụng các biến chưa được khởi tạo.

Chỉnh sửa: Trong mã ở trên tôi đang khởi tạo bộ đệm với giá trị 0, (không phải ký tự '0'), tương đương với khởi tạo nó bằng '\ 0'.

Để làm rõ thêm đoạn mã đầu tiên của tôi, hãy tưởng tượng ví dụ giả tạo sau đây.

char buffer[1024] = {0}; 
for (int i = 0; i < 1000000; ++i) 
{ 
    // Buffer is 0 initialized, so it is fine to call strlen 
    int len = strlen (buffer); 
    memset (buffer, 'a', 1024); 
} 

Lần đầu tiên thông qua các vòng đệm được khởi tạo 0, vì vậy strlen sẽ trở lại 0. Lần thứ hai thông qua các vòng đệm không còn khởi tạo 0, và trong thực tế không chứa một đơn 0 nhân vật, do đó, hành vi của strlen là không xác định.

Vì bạn đã đồng ý với tôi rằng nếu bộ đệm được khởi động, bộ đệm di chuyển bên trong vòng lặp không được khuyến khích và tôi đã cho thấy rằng việc khởi tạo bộ đệm ngoài vòng lặp không bảo vệ, tại sao khởi tạo nó?

+0

Trước tiên, tôi khởi tạo null '\ 0' không phải '0' rất hữu ích vì nhiều lý do. Thứ hai, việc khởi tạo không xảy ra cùng một lần truy cập hiệu suất như bài tập. Đoạn mã thứ hai của bạn, tôi đồng ý, là không thể lưu ý. Tôi cũng không hiểu lý do của bạn về 'lần thứ hai xung quanh'. Tôi không thực sự nghĩ rằng bạn đã giải thích điểm của bạn 0 bạn có thể xây dựng ..? – Konrad

+0

Tôi đã chỉnh sửa bài đăng của mình để làm rõ một trong các ví dụ. –

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