2009-12-29 46 views
36

tôi nghe một câu nói c rằng ++ lập trình viên nên tránh memset,Trình lập trình C++ có nên tránh việc ghi nhớ không?

class ArrInit { 
    //! int a[1024] = { 0 }; 
    int a[1024]; 
public: 
    ArrInit() { memset(a, 0, 1024 * sizeof(int)); } 
}; 

nên xem xét các mã trên, nếu bạn không sử dụng memset, làm thế nào bạn có thể làm một [1..1024] đầy zero? Whats sai với memset trong C + +?

cảm ơn.

+3

Bạn có thể đưa ra lý do tại sao bạn nghĩ người ta không nên ghi nhớ trong C++? Tôi không biết tại sao làm memset nên dẫn đến bất kỳ vấn đề trong C + +. Hãy sửa tôi nếu tôi sai. Cảm ơn! – Jay

+0

Có thể anh ấy đã nghe nó trong ngữ cảnh "không sử dụng memset để không xuất hiện đối tượng lớp". –

+2

@Jay: Chúng ở trên là OK. Nhưng việc sử dụng memset để không phải là đối tượng lớp (không chỉ là một thành viên) cũng không phải là một ý tưởng hay. Điều này đặc biệt là problomatic nếu đối tượng chứa các thành viên có các hàm tạo (thực hiện một số khởi tạo). –

Trả lời

44

Vấn đề không phải là quá nhiều bằng cách sử dụng memset() trên các loại được xây dựng trong, nó được sử dụng chúng trên lớp (aka không POD) loại. Làm như vậy sẽ hầu như luôn làm điều sai trái và thường xuyên làm điều gây tử vong - ví dụ, có thể chà đạp lên một con trỏ bảng chức năng ảo.

+1

bạn có thể thêm ví dụ sử dụng ghi nhớ sai không? –

+6

Sử dụng memset trên bất kỳ lớp nào có chức năng ảo có thể sẽ không tốt. –

+0

@Otto: vì sizeof (lớp) sẽ coi con trỏ bảng chức năng ảo là một thành viên dữ liệu. – Jichao

23

Zero-khởi tạo sẽ giống như thế này:

class ArrInit { 
    int a[1024]; 
public: 
    ArrInit(): a() { } 
}; 

Như việc sử dụng memset, có một vài cách để làm cho việc sử dụng mạnh mẽ hơn (như với tất cả các chức năng như vậy): tránh cứng mã hóa của mảng Kích cỡ và kiểu:

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

Đối với thêm thời gian biên dịch kiểm tra nó cũng có thể để đảm bảo rằng a thực sự là một mảng (vì vậy sizeof(a) sẽ có ý nghĩa):

template <class T, size_t N> 
size_t array_bytes(const T (&)[N]) //accepts only real arrays 
{ 
    return sizeof(T) * N; 
} 

ArrInit() { memset(a, 0, array_bytes(a)); } 

Nhưng đối với các loại không phải ký tự, tôi tưởng tượng giá trị duy nhất bạn muốn sử dụng để điền bằng 0 và không khởi tạo đã có sẵn theo cách này hay cách khác.

+0

nếu muốn khởi tạo mảng với số không khác thì sao? – Jichao

+0

Bạn có thể đặt bất kỳ giá trị nào bạn muốn trong dấu ngoặc ôm (ví dụ: ArrInit(): a() {5}) và nó sẽ khởi tạo mảng với giá trị đó. – Pace

+1

Bạn nhận ra rằng tất cả những gì tôi phải làm là thay đổi 'int' trong ví dụ của bạn thành một lớp nào đó với hàm ảo, và mã của bạn có khả năng xóa sạch vptr, đúng không? Bạn đang giải thích cách gây ra thảm họa theo cách an toàn hơn một chút. –

-3

Trong C++, bạn nên sử dụng mới. Trong trường hợp với các mảng đơn giản như trong ví dụ của bạn thì không có vấn đề gì với việc sử dụng nó. Tuy nhiên, nếu bạn có một mảng các lớp và sử dụng memset để khởi tạo nó, bạn sẽ không xây dựng các lớp đúng cách.

Hãy xem xét điều này:

class A { 
    int i; 

    A() : i(5) {} 
} 

int main() { 
    A a[10]; 
    memset (a, 0, 10 * sizeof (A)); 
} 

Các nhà xây dựng cho mỗi người trong số những yếu tố sẽ không được gọi, do đó biến thành viên tôi sẽ không được thiết lập để 5. Nếu bạn sử dụng mới thay vì:

A a = new A[10]; 

hơn mỗi phần tử trong mảng sẽ có hàm tạo của nó được gọi và tôi sẽ được đặt thành 5.

+0

Tôi đã bỏ lỡ câu hỏi về việc khởi tạo nó về 0 và tập trung vào sự khác biệt giữa memset và mới. – Casey

+1

@Casey: 'A a [1]' trong trình biên dịch g ++ của tôi không gọi hàm khởi tạo và biến memeber tôi sẽ được đặt thành 5. – Jichao

+3

'A a [10] = new A [10];' không hợp lệ C++ . Bạn có vẻ khó hiểu với C++ bằng ngôn ngữ khác. –

0

Mã của bạn là tốt. Tôi nghĩ rằng thời gian duy nhất trong C + +, nơi memset là nguy hiểm là khi bạn làm điều gì đó dọc theo dòng:
YourClass instance; memset(&instance, 0, sizeof(YourClass);.

Tôi tin rằng nó có thể không ra dữ liệu nội bộ trong trường hợp của bạn mà trình biên dịch đã tạo.

8

Đó là "xấu" vì bạn không thực hiện ý định của mình.

Mục đích của bạn là đặt từng giá trị trong mảng thành 0 và những gì bạn đã lập trình đang thiết lập một vùng bộ nhớ thô thành 0. Có, hai điều có cùng tác dụng nhưng rõ ràng hơn là chỉ cần viết mã bằng 0 cho mỗi phần tử.

Ngoài ra, nó có thể không hiệu quả hơn.

class ArrInit 
{ 
public: 
    ArrInit(); 
private: 
    int a[1024]; 
}; 

ArrInit::ArrInit() 
{ 
    for(int i = 0; i < 1024; ++i) { 
     a[i] = 0; 
    } 
} 


int main() 
{ 
    ArrInit a; 
} 

Biên dịch này với Visual C++ 2008 32 bit với optimisations bật biên dịch vòng lặp để -

; Line 12 
    xor eax, eax 
    mov ecx, 1024    ; 00000400H 
    mov edi, edx 
    rep stosd 

Đó là khá nhiều chính xác những gì memset có khả năng sẽ biên dịch để anyway. Nhưng nếu bạn sử dụng memset thì không có phạm vi nào cho trình biên dịch để thực hiện các tối ưu hơn nữa, trong khi bằng cách viết ý định của bạn, trình biên dịch có thể thực hiện các tối ưu hơn nữa, ví dụ như nhận thấy rằng mỗi phần tử sau đó được đặt thành một cái gì đó khác trước khi nó được sử dụng để initialisation có thể được tối ưu hóa ra, mà nó có khả năng không thể làm gần như dễ dàng nếu bạn đã sử dụng memset.

+0

Tôi hiểu tất nhiên là bộ khởi tạo mặc định sẽ không là mảng, vì vậy đây chỉ là một ví dụ nhưng điểm đứng, thực hiện yêu cầu của bạn, trong trường hợp này là đặt mỗi phần tử mảng thành 0, thay vì một số phương thức khác để đạt được các kết quả trừ khi đó là cách duy nhất bạn có thể đạt được các yêu cầu khác như hiệu suất – jcoder

+0

'Đó là chính xác những gì mà bộ nhớ có khả năng sẽ biên dịch thành anyway.' Không, memset có thể phức tạp hơn và hiệu quả hơn một' rep stosd' đơn giản – zhangyoufu

49

Trong C++ std::fill hoặc std::fill_n có thể là lựa chọn tốt hơn, vì nó là chung và do đó có thể hoạt động trên các đối tượng cũng như POD. Tuy nhiên, memset hoạt động trên một chuỗi nguyên byte và do đó sẽ không bao giờ được sử dụng để khởi tạo không phải POD. Bất kể, triển khai tối ưu hóa của std::fill có thể sử dụng chuyên môn nội bộ để gọi memset nếu loại đó là POD.

+1

Tôi quên về std :: điền để +1 này từ tôi. Có, có một hàm C++ được thiết kế đặc biệt để lấp đầy các thùng chứa để sử dụng nó! – jcoder

+4

ý nghĩa của POD là gì? – Jichao

+6

http://en.wikipedia.org/wiki/Plain_old_data_structures – Reunanen

9

Có gì sai với memset trong C++ là chủ yếu là điều tương tự đó là sai với memset trong C. memset lấp đầy khu vực bộ nhớ với mẫu zero-bit vật lý, trong khi trên thực tế trong hầu như 100% các trường hợp, bạn cần phải điền một mảng với logic không có giá trị của loại tương ứng. Trong ngôn ngữ C, memset chỉ được đảm bảo khởi tạo đúng bộ nhớ cho các loại số nguyên (và giá trị của nó là tất cả các loại số nguyên, trái ngược với chỉ loại char, là một bảo đảm tương đối gần đây được thêm vào đặc tả ngôn ngữ C). Nó không được bảo đảm để thiết lập đúng giá trị điểm động bằng 0, nó không được đảm bảo để tạo ra các con trỏ null thích hợp.

Tất nhiên, ở trên có thể được coi là quá mức, vì các tiêu chuẩn và quy ước bổ sung hoạt động trên nền tảng nhất định có thể (và chắc chắn nhất) sẽ mở rộng khả năng áp dụng của memset, nhưng tôi vẫn đề xuất nguyên tắc dao cạo của Occam ở đây: không dựa vào bất kỳ tiêu chuẩn và quy ước nào khác trừ khi bạn thực sự thực sự phải làm vậy. Ngôn ngữ C++ (cũng như C) cung cấp một số tính năng cấp độ ngôn ngữ cho phép bạn khởi tạo một cách an toàn các đối tượng tổng hợp của mình với các giá trị bằng không đúng loại thích hợp. Các câu trả lời khác đã đề cập đến các tính năng này.

+1

Sự khác nhau giữa số không logic và vật lý là gì? – Adil

+0

@Adil Physical zero là mẫu bit "all-zeros" thực tế rõ ràng trong bộ nhớ. Không logic là mẫu bit có khả năng không được hiểu là giá trị 0 của một kiểu nào đó bằng ngôn ngữ (C hoặc C++ trong trường hợp của chúng ta). – AnT

0

Ngoài tính xấu khi áp dụng cho các lớp học, memset cũng dễ bị lỗi. Nó rất dễ dàng để có được các đối số out-of-order, hoặc để quên các phần sizeof. Mã sẽ thường biên dịch với những lỗi này, và lặng lẽ làm điều sai trái. Các triệu chứng của lỗi có thể không biểu hiện cho đến sau này, khiến việc theo dõi trở nên khó khăn.

memset cũng có vấn đề với nhiều loại đồng bằng, như con trỏ và điểm nổi. Một số lập trình viên đặt tất cả byte thành 0, giả sử con trỏ sẽ là NULL và float sẽ là 0.0. Đó không phải là một giả định di động.

+0

Con trỏ thiết lập và số dấu phẩy động đến số nhị phân thường hoạt động, nhưng tôi không muốn tham gia vào thói quen. Tuy nhiên, tiêu chuẩn điểm nổi IEEE ngày càng trở nên cố thủ hơn, và điều đó giải thích tất cả bit-zero bằng 0.0. –

+0

@ David: Vâng, nó thường hoạt động, nhưng một ngày nào đó bạn sẽ ở trên một nền tảng mà nó không có. –

0

Không có lý do thực sự để không sử dụng nó ngoại trừ vài trường hợp người ta đã chỉ ra rằng không ai sử dụng dù sao, nhưng không có lợi ích thực sự khi sử dụng hoặc trừ khi bạn đang lấp đầy memguards hoặc gì đó.

0

Câu trả lời ngắn sẽ được sử dụng một std :: vector với kích thước ban đầu của 1024.

std::vector<int> a(1024); // Uses the types default constructor, "T()". 

Giá trị ban đầu của tất cả các yếu tố của "a" sẽ là 0, như std :: vector (constructor) (kích thước) (cũng như vector :: resize) sao chép giá trị của hàm tạo mặc định cho tất cả các phần tử.Đối với các loại built-in (aka loại nội tại, hoặc PODs), bạn được đảm bảo giá trị ban đầu là 0:

int x = int(); // x == 0 

này sẽ cho phép các kiểu đó "a" sử dụng để thay đổi với fuss tối thiểu, thậm chí đó của một lớp học.

Hầu hết các hàm có con trỏ void (void *) làm tham số, chẳng hạn như memset, không được nhập an toàn. Bỏ qua kiểu của một đối tượng, theo cách này, loại bỏ tất cả các đối tượng ngữ nghĩa kiểu C++ có xu hướng dựa vào, chẳng hạn như xây dựng, hủy và sao chép. memset làm cho các giả định về một lớp, vi phạm trừu tượng (không biết hoặc quan tâm những gì bên trong một lớp). Mặc dù vi phạm này không phải luôn luôn rõ ràng ngay lập tức, đặc biệt là với các loại nội tại, nó có khả năng dẫn đến khó xác định lỗi, đặc biệt khi cơ sở mã phát triển và thay đổi tay. Nếu kiểu đó là memset là một lớp với một vtable (các hàm ảo) nó cũng sẽ ghi đè lên dữ liệu đó.

1

Đây là một chủ đề OLD, nhưng đây là một twist thú vị:

class myclass 
{ 
    virtual void somefunc(); 
}; 

myclass onemyclass; 

memset(&onemyclass,0,sizeof(myclass)); 

hoạt động hoàn hảo tốt!

Tuy nhiên,

myclass *myptr; 

myptr=&onemyclass; 

memset(myptr,0,sizeof(myclass)); 

thực sự đặt virtuals (nghĩa là somefunc() ở trên) để NULL.

Cho rằng bộ nhớ nhanh hơn rất nhiều so với cài đặt thành 0 và mỗi thành viên trong một lớp lớn, tôi đã thực hiện bộ nhớ đầu tiên ở trên cho các độ tuổi và không bao giờ gặp sự cố.

Vì vậy, câu hỏi thực sự thú vị là nó hoạt động như thế nào? Tôi cho rằng trình biên dịch thực sự bắt đầu đặt BEYOND của zero là bảng ảo ... bất kỳ ý tưởng nào?

+0

"nó không sụp đổ hoặc làm bất cứ điều gì rõ ràng là sai mà tôi có thể nhìn thấy" và "nó hoạt động" là rất nhiều không giống nhau. AFAICT cả hai đoạn mã trên đều giống nhau, nhưng một khi bạn bắt đầu gọi hành vi không xác định, tất cả các cược sẽ bị tắt. Nhiều khả năng một chương trình thực hiện một trong những điều trên sẽ chỉ xuất hiện trong các trường hợp rất cụ thể và sẽ phá vỡ nghiêm trọng trong các trường hợp khác (ví dụ: trên trình biên dịch khác, hoặc hệ điều hành hoặc kiến ​​trúc CPU) –