2010-01-11 25 views
9

Gần đây tôi đã gặp phải một trường hợp cần so sánh hai tệp (vàng và dự kiến) để xác minh kết quả kiểm tra và mặc dù dữ liệu được ghi cho cả hai tệp giống nhau, tệp không khớp. Khi điều tra thêm, tôi thấy rằng có một cấu trúc chứa một số số nguyên và mảng char là 64 byte, và không phải tất cả các byte của mảng char đều được sử dụng trong hầu hết các trường hợp và các trường không được sử dụng từ mảng chứa dữ liệu ngẫu nhiên và điều đó gây ra sự không khớp.Có thực hành tốt để khởi tạo mảng trong C/C++ không?

Điều này mang lại cho tôi đặt câu hỏi cho dù đó là thực hành tốt để khởi tạo mảng trong C/C++ là tốt, vì nó được thực hiện trong Java?

Trả lời

23

Thực hành tốt để khởi tạo bộ nhớ/biến trước khi bạn sử dụng chúng - biến chưa được khởi tạo là một nguồn lớn các lỗi thường rất khó theo dõi.

Khởi tạo tất cả dữ liệu là một ý tưởng rất hay khi viết nó thành định dạng tệp: Nó giữ nội dung tệp gọn hơn để dễ làm việc hơn, ít bị lỗi nếu ai đó cố gắng "sử dụng" dữ liệu chưa được khởi tạo (Hãy nhớ rằng nó có thể không chỉ là mã của riêng bạn mà đọc dữ liệu trong tương lai), và làm cho các tập tin nén nhiều hơn nữa.

Lý do chính đáng để không khởi tạo biến trước khi bạn sử dụng chúng là trong các tình huống hiệu suất quan trọng, nơi khởi tạo về mặt kỹ thuật là "không cần thiết" và phải gánh chịu chi phí đáng kể. Nhưng trong hầu hết các trường hợp, các biến khởi tạo sẽ không gây hại đáng kể (đặc biệt nếu chúng chỉ được khai báo ngay trước khi chúng được sử dụng), nhưng sẽ giúp bạn tiết kiệm rất nhiều thời gian phát triển bằng cách loại bỏ một nguồn lỗi phổ biến.

+7

chỉ là một vài gợi ý đi theo cùng một hướng: sử dụng phạm vi nhỏ nhất có thể cho các biến của bạn Khởi tạo trực tiếp sau khi khai báo.Chỉ sử dụng các biến cho một mục đích Sử dụng const nếu bạn dự định biến không thay đổi Lý do chính cho những gợi ý này: chúng cải thiện khả năng đọc và r giáo dục cơ hội cho các lỗi tinh tế trong quá trình thay đổi mã. Hiện tại, mã có thể không có lỗi, không theo các gợi ý này - nhưng theo sau chúng sẽ dễ dàng hơn nếu không bị lỗi với mỗi thay đổi mã. –

+1

Xác định và khởi tạo trong một câu lệnh bất cứ khi nào có thể. –

+1

Không, khởi tạo biến của bạn nếu thích hợp. Vòng lặp for có thể đọc được nhiều hơn khi biến vòng lặp được khởi tạo trong câu lệnh for. Biến của bạn có thể đã được khai báo trước vòng lặp. –

1

Nếu bạn không khởi tạo các giá trị trong mảng C++, thì giá trị có thể là bất kỳ thứ gì, vì vậy sẽ tốt hơn nếu bạn không có kết quả dự đoán.

Nhưng nếu bạn sử dụng mảng char như một chuỗi bị chấm dứt null, thì bạn sẽ có thể ghi nó vào một tệp có chức năng thích hợp.

Mặc dù trong C++, tốt hơn nên sử dụng giải pháp OOP. I E. vectơ, dây, vv

+0

Không, nếu bạn truy cập bộ nhớ chưa được khởi tạo có lỗi trong chương trình của bạn. Các biến thường tạo ra các truy cập ghi không cần thiết –

5

Sử dụng giá trị không xác định trong một mảng dẫn đến hành vi không xác định. Vì vậy, chương trình này miễn phí để tạo ra các kết quả khác nhau. Điều này có thể có nghĩa là các tệp của bạn kết thúc hơi khác hoặc chương trình gặp sự cố hoặc chương trình định dạng ổ cứng của bạn hoặc chương trình khiến cho ma quỷ bay ra mũi người dùng (http://catb.org/jargon/html/N/nasal-demons.html)

Điều này không có nghĩa là bạn cần xác định giá trị mảng của bạn khi bạn tạo mảng, nhưng bạn phải đảm bảo bạn khởi tạo bất kỳ giá trị mảng nào trước khi sử dụng nó. Tất nhiên, cách đơn giản nhất để đảm bảo điều này là thực hiện điều này khi bạn tạo mảng.

MyStruct array[10]; 
printf("%f", array[2].v); // POTENTIAL BANG! 
array[3].v = 7.0; 
... 
printf("%f", array[3].v); // THIS IS OK. 

Đừng quên rằng đối với mảng lớn PODs có một cách viết tắt tốt đẹp để khởi tất cả các thành viên để không

MyPODStruct bigArray[1000] = { 0 }; 
+0

+1 để viết tắt, tôi sử dụng phong cách này một số tiền hợp lý. Một lời cảnh cáo mà không có bằng chứng cụ thể của nó, nhưng tôi tin rằng định dạng = {0} có thể được tối ưu hóa giống như Win32 ZeroMemory (http://msdn.microsoft.com/en-us/library/aa366920%28VS.85 % 29.aspx) API, tùy thuộc vào các trình biên dịch mong muốn và cài đặt tối ưu hóa. – dirtybird

+0

Nó chỉ có thể được tối ưu hóa nếu trình biên dịch có thể chứng minh rằng nó sẽ không tạo ra sự khác biệt quan sát được (tức là, tất cả các giá trị đều được thiết đặt bằng nhau). Trong trường hợp này, với mảng được ghi vào một tập tin, nó chắc chắn sẽ tạo ra một sự khác biệt quan sát được. – caf

0

Trước tiên, bạn nên khởi tạo mảng, các biến, vv nếu không làm như vậy sẽ gây rối với tính chính xác của chương trình của bạn.

Thứ hai, có vẻ như trong trường hợp cụ thể này, không khởi tạo mảng không ảnh hưởng đến tính chính xác của chương trình gốc.Thay vào đó, chương trình có nghĩa là so sánh các tệp không biết đủ về định dạng tệp được sử dụng để biết các tệp khác nhau theo cách có ý nghĩa ("có ý nghĩa" được chương trình đầu tiên xác định).

Thay vì phàn nàn về chương trình gốc, tôi sẽ sửa chương trình so sánh để biết thêm về định dạng tệp được đề cập. Nếu định dạng tệp không được tài liệu tốt thì bạn có lý do chính đáng để khiếu nại.

3

Tôi rất không đồng ý với ý kiến ​​đã cho rằng làm như vậy là "loại bỏ một nguồn lỗi phổ biến" hoặc "không làm như vậy sẽ gây rối với tính chính xác của chương trình". Nếu chương trình làm việc với các giá trị đơn vị hóa thì nó có lỗi và không chính xác. Việc khởi tạo các giá trị không loại trừ lỗi này, bởi vì chúng thường không có giá trị mong đợi ở lần sử dụng đầu tiên. Tuy nhiên, khi chúng chứa rác ngẫu nhiên, chương trình có nhiều khả năng bị hỏng một cách ngẫu nhiên trong mọi lần thử. Luôn luôn có các giá trị giống nhau có thể mang lại một hành vi xác định hơn trong sự cố và làm cho việc gỡ lỗi dễ dàng hơn.

Đối với câu hỏi cụ thể của bạn, đó cũng là biện pháp bảo mật tốt để ghi đè các phần không sử dụng trước khi chúng được ghi vào tệp, vì chúng có thể chứa nội dung nào đó từ lần sử dụng trước đó mà bạn không muốn được viết, như mật khẩu.

+0

+1. Tôi đã thấy folks chỉ tin rằng khởi tạo một biến giải quyết tất cả các vấn đề. Không ... nó chỉ giúp khi bạn khởi tạo con trỏ đó thành NULL mà kaboom * luôn luôn * xảy ra thay vì chỉ thấy vấn đề cắt lên ở đây và ở đó và kéo tóc ra cố gắng theo dõi nó xuống. Ngoài ra còn có các tình huống mà bạn đang gỡ rối và thực thi cùng một đốm màu trong vài lần, và nếu bạn có lỗi logic, bạn có thể gặp các đường dẫn không mong muốn vì biến/mảng/etc chỉ xảy ra với cùng một bộ nhớ vị trí, sử dụng giá trị trước đó. – dirtybird

0

Tôi sẽ nói rằng thực hành tốt trong C++ đang sử dụng tiêu chuẩn :: vector <> thay vì mảng. Điều này là không hợp lệ cho C, tất nhiên.

1

Hãy nhớ rằng việc giữ mảng chưa được khởi tạo có thể có các ưu điểm như hiệu suất.

Chỉ xấu đọc từ mảng chưa được khởi tạo. Có chúng xung quanh mà không bao giờ đọc từ những nơi chưa được khởi tạo là tốt.

Hơn nữa, nếu chương trình của bạn có lỗi làm cho nó đọc từ vị trí không được khởi tạo trong mảng, thì "che đậy nó" bằng cách khởi tạo tất cả các mảng để biết giá trị đã biết không phải là giải pháp cho lỗi, và chỉ có thể làm cho nó bề mặt sau.

+0

+1 Điều đó xảy ra thường xuyên hơn người ta nghĩ. –

1

Người ta có thể viết một bài viết lớn về sự khác biệt giữa hai kiểu mà người ta có thể gặp phải, những người khởi tạo biến luôn khi khai báo họ và những người khởi tạo chúng khi cần thiết. Tôi chia sẻ một dự án lớn với một người đang ở trong hạng mục đầu tiên và bây giờ tôi chắc chắn là loại thứ hai. Luôn khởi tạo các biến đã mang lại nhiều lỗi và vấn đề phức tạp hơn không và tôi sẽ cố gắng giải thích lý do tại sao, ghi nhớ các trường hợp tôi tìm thấy. Ví dụ đầu tiên:

struct NODE Pop(STACK * Stack) 
{ 
    struct NODE node = EMPTY_STACK; 

    if(Stack && Stack->stackPointer) 
    node = Stack->node[--Stack->stackPointer]; 

    return node; 
} 

Đây là mã được viết bởi người kia. Hàm này là hàm nóng nhất trong ứng dụng của chúng ta (bạn tưởng tượng một chỉ mục văn bản trên 500 000 000 câu trong một cây bậc ba, ngăn xếp FIFO được sử dụng để xử lý đệ quy vì chúng tôi không muốn sử dụng các hàm gọi đệ quy). Đây là điển hình của phong cách lập trình của anh ấy vì khởi tạo hệ thống các biến. Vấn đề với mã đó là memcpy ẩn của bản khởi tạo và hai bản sao khác của cấu trúc (mà btw không được gọi tới đôi khi làgcc), vì vậy chúng tôi có 3 bản sao + một cuộc gọi hàm ẩn trong chức năng nóng nhất của dự án . Viết lại nó để

struct NODE Pop(STACK * Stack) 
{ 
    if(Stack && Stack->stackPointer) 
    return Stack->node[--Stack->stackPointer]; 
    return EMPTY_STACK; 
} 

Chỉ có một bản sao (và lợi ích bổ sung trên SPARC nơi nó chạy, chức năng là một chức năng lá nhờ cuộc gọi tránh để memcpy và không cần phải xây dựng một cửa sổ đăng ký mới). Vì vậy, chức năng nhanh gấp 4 lần.

Một vấn đề khác tôi tìm thấy ounce nhưng không nhớ chính xác (vì vậy không có ví dụ mã, xin lỗi). Một biến được khởi tạo khi khai báo nhưng nó được sử dụng trong một vòng lặp, với switch trong một automaton trạng thái hữu hạn. Vấn đề giá trị khởi tạo không phải là một trong các trạng thái của automaton và trong một số trường hợp cực kỳ hiếm hoi mà automaton không hoạt động chính xác. Bằng cách loại bỏ bộ khởi tạo, cảnh báo trình biên dịch phát ra đã làm rõ ràng rằng biến có thể được sử dụng trước khi nó được khởi tạo đúng cách. Sửa chữa các automaton đã được dễ dàng sau đó. Đạo đức: phòng thủ khởi tạo một biến có thể ngăn chặn một cảnh báo rất hữu ích của trình biên dịch.

Kết luận: Khởi tạo biến của bạn một cách khôn ngoan. Làm cho nó hệ thống là không có gì nhiều hơn là theo đuổi hàng hóa (bạn thân của tôi trong công việc là người vận chuyển hàng hóa tồi tệ nhất có thể tưởng tượng, anh ta không bao giờ sử dụng goto, luôn khởi tạo biến, sử dụng nhiều khai báo tĩnh (nó nhanh hơn bạn biết) trong thực tế thậm chí còn thực sự chậm trên SPARC 64bit), làm cho tất cả các chức năng inline ngay cả khi chúng có 500 dòng (sử dụng __attribute__((always_inline)) khi trình biên dịch không muốn)

+0

+1, cảm ơn người đàn ông lành mạnh cho câu trả lời đó. –

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