Tại sao sử dụng bzero hơn memset?


156

Trong lớp Lập trình hệ thống tôi đã học kỳ trước, chúng tôi phải triển khai một máy khách / máy chủ cơ bản trong C. Khi khởi tạo các cấu trúc, như sock_addr_in, hoặc bộ đệm char (mà chúng tôi thường gửi dữ liệu qua lại giữa máy khách và máy chủ), giáo sư hướng dẫn chúng tôi chỉ sử dụng bzerovà không memsetkhởi tạo chúng. Anh ấy không bao giờ giải thích tại sao, và tôi tò mò liệu có lý do hợp lệ cho việc này không?

Tôi thấy ở đây: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown đó bzerolà hiệu quả hơn do thực tế rằng chỉ bao giờ sẽ được zeroing bộ nhớ, vì vậy nó không phải làm bất kỳ kiểm tra bổ sung memsetcó thể làm. Điều đó vẫn không nhất thiết có vẻ như là một lý do để hoàn toàn không sử dụng memsetcho bộ nhớ zeroing.

bzerođược coi là không dùng nữa, và hơn nữa không phải là hàm C tiêu chuẩn. Theo hướng dẫn, memsetđược ưa thích hơn bzerovì lý do này. Vậy tại sao bạn vẫn muốn sử dụng bzerohơn memset? Chỉ để đạt được hiệu quả, hoặc nó là một cái gì đó nhiều hơn? Tương tự như vậy, những lợi ích của những gì memsettrên bzeromà làm cho nó trên thực tế lựa chọn ưa thích cho các chương trình mới hơn?


28
"Tại sao sử dụng bzero trên memset?" - Đừng. Memset là tiêu chuẩn, bzero không.

30
bzero là một BSDism (). bộ nhớ () là ansi-c. ngày nay, bzero () có thể sẽ được triển khai dưới dạng macro. Đừng yêu cầu giáo sư của bạn tự cạo râu và đọc một số sách. hiệu quả là một lập luận không có thật. Một tòa nhà chọc trời hoặc chuyển đổi ngữ cảnh có thể dễ dàng tiêu tốn hàng chục nghìn tích tắc đồng hồ, một lần vượt qua một bộ đệm chạy ở tốc độ xe buýt. Nếu bạn muốn tối ưu hóa các chương trình mạng: giảm thiểu số lượng tòa nhà (bằng cách đọc / viết các phần lớn hơn)
wildplasser

7
Ý tưởng memsetcó thể kém hiệu quả hơn một chút vì "kiểm tra thêm một chút" chắc chắn là một trường hợp tối ưu hóa sớm: bất kỳ lợi ích nào bạn có thể thấy từ việc bỏ qua một lệnh CPU hoặc hai đều không đáng khi bạn có thể gây nguy hiểm cho tính di động của bạn mã. bzerođã lỗi thời và đó là lý do đủ để không sử dụng nó.
dasblinkenlight

4
Thông thường, thay vào đó, bạn có thể thêm một trình khởi tạo `= {0}` và không gọi hàm nào cả. Điều này trở nên dễ dàng hơn khi khoảng đầu thế kỷ C ngừng yêu cầu khai báo trước các biến cục bộ. Một số giấy tờ thực sự cũ vẫn còn bị mắc kẹt sâu trong thế kỷ trước, mặc dù.
MSalters

1
@SSAnne không, nhưng rất có thể nó bắt nguồn từ một cuốn sách được đề xuất cho khóa học mà anh ấy đã bị ảnh hưởng, như được đề cập trong một trong những câu trả lời dưới đây: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Câu trả lời:


152

Tôi không thấy bất kỳ lý do để thích bzerohơn memset.

memsetlà một hàm C tiêu chuẩn trong khi bzerochưa bao giờ là một hàm chuẩn C. Lý do có lẽ là do bạn có thể đạt được chính xác chức năng tương tự bằng cách sử dụng memsetchức năng.

Bây giờ liên quan đến hiệu quả, các trình biên dịch như gccsử dụng các triển khai dựng sẵn để memsetchuyển sang một triển khai cụ thể khi 0phát hiện một hằng số . Tương tự glibckhi các nội dung bị vô hiệu hóa.


Cảm ơn. Điều này thật ý nghĩa. Tôi khá chắc chắn rằng nó memsetluôn luôn được sử dụng trong trường hợp này, nhưng bối rối là tại sao chúng ta không sử dụng nó. Cảm ơn đã làm rõ, và khẳng định lại suy nghĩ của tôi.
PseudoPsyche

1
Tôi đã có nhiều vấn đề với bzeroviệc triển khai bị hỏng . Trên các mảng không liên kết, nó được sử dụng để vượt quá độ dài được cung cấp và bỏ ra thêm một chút byte. Không bao giờ có một vấn đề như vậy sau khi chuyển sang memset.
rustyx

Đừng quên memset_snên sử dụng cái nào nếu bạn muốn đảm bảo trình biên dịch không âm thầm tối ưu hóa cuộc gọi để "xóa" bộ nhớ cho một số mục đích liên quan đến bảo mật (chẳng hạn như xóa sạch vùng bộ nhớ có độ nhạy mẩu thông tin như mật khẩu Cleartext).
Christopher Schultz

69

Tôi đoán bạn đã sử dụng (hoặc giáo viên của bạn bị ảnh hưởng bởi) Lập trình mạng UNIX bởi W. Richard Stevens. Ông sử dụng bzerothường xuyên thay vì memset, ngay cả trong phiên bản cập nhật nhất. Cuốn sách rất nổi tiếng, tôi nghĩ nó trở thành một thành ngữ trong lập trình mạng, đó là lý do tại sao bạn vẫn thấy nó được sử dụng.

Tôi sẽ gắn bó memsetđơn giản vì bzerobị phản đối và giảm tính di động. Tôi nghi ngờ bạn sẽ thấy bất kỳ lợi ích thực sự nào từ việc sử dụng cái này hơn cái kia.


4
Bạn sẽ đúng. Chúng tôi không yêu cầu sách giáo khoa cho khóa học này, nhưng tôi chỉ kiểm tra lại giáo trình và Lập trình mạng UNIX thực sự được liệt kê dưới dạng tài nguyên tùy chọn. Cảm ơn.
PseudoPsyche

9
Nó thực sự tồi tệ hơn thế. Nó không được chấp nhận trong POSIX.1-2001 và bị xóa trong POSIX.1-2008.
paxdiablo

9
Trích dẫn trang 8 của phiên bản thứ ba của Lập trình mạng UNIX của W. Richard Stevens - Thật vậy, tác giả của TCPv3 đã mắc lỗi khi hoán đổi các đối số thứ hai và thứ ba để ghi nhớ trong 10 lần xuất hiện lần đầu tiên. Trình biên dịch AC không thể bắt lỗi này vì cả hai lần xuất hiện đều giống nhau ... đó là lỗi và có thể tránh được khi sử dụng bzero, vì việc hoán đổi hai đối số thành bzero sẽ luôn bị trình biên dịch C bắt nếu sử dụng các nguyên mẫu hàm. Tuy nhiên như paxdiablo đã chỉ ra, bzero không được dùng nữa.
Aaron Newton

@AaronNewton, bạn nên thêm câu đó vào câu trả lời của Michael vì nó xác nhận những gì anh ấy nói.
Synetech

52

Một lợi thế mà tôi nghĩ bzero()đã vượt qua memset()khi đặt bộ nhớ về 0 là giảm khả năng xảy ra lỗi.

Đã hơn một lần tôi gặp phải một lỗi giống như:

memset(someobject, size_of_object, 0);    // clear object

Trình biên dịch sẽ không phàn nàn (mặc dù có thể tăng một số mức cảnh báo có thể trên một số trình biên dịch) và hậu quả sẽ là bộ nhớ không bị xóa. Bởi vì điều này không làm hỏng đối tượng - nó chỉ để nó một mình - có một cơ hội tốt rằng lỗi có thể không biểu hiện thành bất cứ điều gì rõ ràng.

Thực tế bzero()không phải là tiêu chuẩn là một kích thích nhỏ. (FWIW, tôi sẽ không ngạc nhiên nếu hầu hết các lệnh gọi chức năng trong các chương trình của tôi đều không chuẩn; thực tế việc viết các hàm như vậy là công việc của tôi).

Trong một bình luận cho một câu trả lời khác ở đây, Aaron Newton đã trích dẫn như sau từ Lập trình mạng Unix, Tập 1, Ấn bản thứ 3 của Stevens, và cộng sự, Phần 1.2 (nhấn mạnh thêm):

bzerokhông phải là chức năng ANSI C. Nó có nguồn gốc từ mã mạng Berkely sớm. Tuy nhiên, chúng tôi sử dụng nó trong toàn bộ văn bản, thay vì memsetchức năng ANSI C , bởi vìbzero dễ nhớ hơn (chỉ có hai đối số) so với memset(với ba đối số). Hầu như mọi nhà cung cấp hỗ trợ API socket cũng cung cấp bzerovà nếu không, chúng tôi cung cấp định nghĩa macro trong unp.htiêu đề của chúng tôi .

Thật vậy, tác giả của TCPv3 [TCP / IP Illustrated, Tập 3 - Stevens 1996] đã phạm sai lầm khi hoán đổi các đối số thứ hai và thứ ba thànhmemset 10 lần xuất hiện trong lần in đầu tiên . Trình biên dịch AC không thể bắt lỗi này vì cả hai đối số đều cùng loại. (Trên thực tế, đối số thứ hai là một intvà đối số thứ ba size_t, thường là một unsigned int, nhưng các giá trị được chỉ định, 0 và 16, tương ứng, vẫn được chấp nhận cho loại đối số khác.) Cuộc gọi memsetvẫn hoạt động, bởi vì chỉ một Một số chức năng của ổ cắm thực sự yêu cầu 8 byte cuối cùng của cấu trúc địa chỉ ổ cắm Internet được đặt thành 0. Tuy nhiên, đó là một lỗi và có thể tránh được bằng cách sử dụng bzero, bởi vì việc hoán đổi hai đối số bzerosẽ luôn bị trình biên dịch C bắt nếu các nguyên mẫu hàm được sử dụng.

Tôi cũng tin rằng phần lớn các cuộc gọi đến memset()không có bộ nhớ, vậy tại sao không sử dụng API phù hợp với trường hợp sử dụng đó?

Một nhược điểm có thể xảy ra bzero()là trình biên dịch có thể có khả năng tối ưu hóa cao hơn memcpy()vì nó là tiêu chuẩn và do đó chúng có thể được viết để nhận ra nó. Tuy nhiên, hãy nhớ rằng mã chính xác vẫn tốt hơn mã không chính xác đã được tối ưu hóa. Trong hầu hết các trường hợp, việc sử dụng bzero()sẽ không gây ra tác động đáng chú ý đến hiệu suất chương trình của bạn và đó bzero()có thể là một chức năng vĩ mô hoặc nội tuyến mở rộng memcpy().


Vâng, tôi cho rằng đây có thể là một lý do khi làm việc trong môi trường lớp học như thế này, để làm cho nó có khả năng ít gây nhầm lẫn hơn cho các sinh viên. Tôi không nghĩ rằng đây là trường hợp với giáo sư của tôi, tuy nhiên. Ông là một giáo viên RTFM rất lớn. Nếu bạn có một câu hỏi có thể được trả lời bằng hướng dẫn, anh ấy sẽ kéo các trang nam trên máy chiếu trong lớp và cho bạn xem. Anh ấy đã nói rất nhiều về việc ăn sâu vào tâm trí của mọi người rằng sách hướng dẫn sẽ được đọc và trả lời hầu hết các câu hỏi của bạn. Tôi rất biết ơn vì điều này, trái ngược với một số giáo sư khác.
PseudoPsyche

5
Tôi nghĩ rằng đây là một đối số có thể được đưa ra ngay cả bên ngoài lớp học - tôi đã thấy lỗi này trong mã sản xuất. Nó đánh tôi là một sai lầm dễ mắc phải. Tôi cũng đoán rằng phần lớn các memset()cuộc gọi chỉ đơn giản là loại bỏ một khối bộ nhớ, mà tôi nghĩ là một đối số khác bzero(). Những gì 'b' bzero()đứng cho dù sao?
Michael Burr

7
+1. Điều đó memsetvi phạm thứ tự tham số phổ biến của "buffer, buffer_size" khiến IMO đặc biệt dễ bị lỗi.
jamesdlin

Trong Pascal, họ tránh điều đó bằng cách gọi nó là "fillar" và phải mất một char. Hầu hết các trình biên dịch C / C ++ sẽ chọn trình biên dịch đó. Điều này khiến tôi tự hỏi tại sao trình biên dịch không nói "bạn đang truyền con trỏ 32/64 bit trong đó một byte được mong đợi" và khiến bạn chắc chắn trong các lỗi trình biên dịch.
Móż

1
@Gewure đối số thứ hai và thứ ba là sai thứ tự; cuộc gọi chức năng trích dẫn thực hiện chính xác không có gì
Ichthyo

4

Muốn đề cập đến một cái gì đó về bzero so với đối số memset. Cài đặt ltrace và sau đó so sánh những gì nó làm dưới mui xe. Trên Linux với libc6 (2.19-0ubfox6.6), các cuộc gọi được thực hiện hoàn toàn giống nhau (thông qua ltrace ./test123):

long m[] = {0}; // generates a call to memset(0x7fffefa28238, '\0', 8)
int* p;
bzero(&p, 4);   // generates a call to memset(0x7fffefa28230, '\0', 4)

Tôi đã được thông báo rằng trừ khi tôi đang làm việc trong phần sâu của libc hoặc bất kỳ số giao diện kernel / syscall nào, tôi không phải lo lắng về chúng. Tất cả những gì tôi nên lo lắng là cuộc gọi đáp ứng yêu cầu của bộ đệm. Những người khác đã đề cập về cái nào thích hợp hơn cái kia nên tôi sẽ dừng ở đây.


Điều này xảy ra vì một số phiên bản GCC sẽ phát mã memset(ptr, 0, n)khi họ nhìn thấy bzero(ptr, n)và họ không thể chuyển đổi nó thành mã nội tuyến.
zwol

@zwol Nó thực sự là một macro.
SS Anne

1
@SSAnne gcc 9.3 trên máy tính của tôi tự thực hiện chuyển đổi này mà không cần bất kỳ trợ giúp nào từ các macro trong tiêu đề hệ thống. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }tạo ra một cuộc gọi đến memset. (Bao gồm stddef.hcho size_tmà không cần bất cứ điều gì khác mà có thể can thiệp.)
Zwol

4

Bạn có thể không nên sử dụngbzero , nó không thực sự là tiêu chuẩn C, đó là một thứ POSIX.

Và lưu ý rằng từ "đã" - nó không được dùng trong POSIX.1-2001 và bị xóa trong POSIX.1-2008 để bảo vệ bộ nhớ để bạn sử dụng chức năng C chuẩn hơn.


Bạn có ý nghĩa gì bởi tiêu chuẩn C? Bạn có nghĩa là nó không được tìm thấy trong thư viện C tiêu chuẩn?
Koray Tugay

@Koray, tiêu chuẩn C có nghĩa là tiêu chuẩn ISO và, vâng, bzerokhông phải là một phần của điều đó.
paxdiablo

Không, ý tôi là, tôi không biết ý của bạn là gì theo tiêu chuẩn. Có phải tiêu chuẩn ISO có nghĩa là thư viện C tiêu chuẩn? Điều đó đi kèm với ngôn ngữ? Thư viện tối thiểu mà chúng ta biết nó sẽ ở đó?
Koray Tugay

2
@Koray, ISO là tổ chức tiêu chuẩn chịu trách nhiệm về tiêu chuẩn C, hiện tại là C11 và trước đó là C99 và C89. Họ đặt ra các quy tắc mà việc triển khai phải tuân theo để được xem xét C. Vì vậy, nếu tiêu chuẩn nói rằng việc triển khai phải cung cấp bộ nhớ, nó sẽ ở đó cho bạn. Nếu không, đó không phải là C.
paxdiablo

2

Đối với chức năng ghi nhớ, đối số thứ hai là một intvà đối số thứ ba là size_t,

void *memset(void *s, int c, size_t n);

thường là một unsigned int, nhưng nếu các giá trị như, 0 and 16đối số thứ hai và thứ ba tương ứng được nhập sai thứ tự là 16 và 0 thì một lệnh gọi bộ nhớ như vậy vẫn có thể hoạt động, nhưng sẽ không làm gì cả. Bởi vì số lượng byte để khởi tạo được chỉ định là 0.

void bzero(void *s, size_t n)

Một lỗi như vậy có thể tránh được bằng cách sử dụng bzero, bởi vì việc hoán đổi hai đối số thành bzero sẽ luôn bị trình biên dịch C bắt nếu sử dụng các nguyên mẫu hàm.


1
Một lỗi như vậy cũng có thể tránh được với bộ nhớ nếu bạn chỉ nghĩ đơn giản là cuộc gọi là "đặt bộ nhớ này thành giá trị này cho kích thước này" hoặc nếu bạn có một IDE cung cấp cho bạn nguyên mẫu hoặc ngay cả khi bạn chỉ biết bạn là gì làm :-)
paxdiablo

Đồng ý, nhưng chức năng này được tạo ra tại thời điểm các IDE thông minh như vậy không có sẵn để hỗ trợ.
havish

2

Tóm lại: memset yêu cầu nhiều hoạt động lắp ráp hơn sau đóbzero .

Đây là nguồn: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown


Vâng, đó là một điều mà tôi đã đề cập trong OP. Tôi thậm chí thực sự liên kết đến trang chính xác đó. Nó chỉ ra rằng dường như không thực sự tạo ra nhiều sự khác biệt do một số tối ưu hóa trình biên dịch. Để biết thêm chi tiết, xem câu trả lời được chấp nhận bởi ouah.
PseudoPsyche

6
Điều này chỉ cho thấy rằng việc thực hiện bộ nhớ rác là chậm. Trên MacOS X và một số hệ thống khác, memset sử dụng mã được thiết lập khi khởi động tùy thuộc vào bộ xử lý mà bạn đang sử dụng, sử dụng đầy đủ các thanh ghi vector và đối với kích thước lớn, nó sử dụng các hướng dẫn tìm nạp trước một cách thông minh để có được bit cuối cùng của tốc độ.
gnasher729

Ít hướng dẫn hơn không có nghĩa là thực hiện nhanh hơn. Trong thực tế, tối ưu hóa thường làm tăng kích thước nhị phân và số lượng hướng dẫn do không kiểm soát vòng lặp, nội tuyến chức năng, căn chỉnh vòng lặp ... Hãy xem bất kỳ mã được tối ưu hóa hợp lý nào và bạn sẽ thấy nó thường có nhiều hướng dẫn hơn so với triển khai shitty
phuclv

2

Có bất cứ cách nào bạn thích. :-)

#ifndef bzero
#define bzero(d,n) memset((d),0,(n))
#endif

Lưu ý rằng:

  1. Bản gốc bzerokhông trả về gì, memsettrả về void con trỏ ( d). Điều này có thể được sửa bằng cách thêm typecast vào void trong định nghĩa.
  2. #ifndef bzerokhông ngăn bạn ẩn chức năng ban đầu ngay cả khi nó tồn tại. Nó kiểm tra sự tồn tại của một vĩ mô. Điều này có thể gây ra nhiều nhầm lẫn.
  3. Không thể tạo con trỏ hàm tới macro. Khi sử dụng bzerothông qua con trỏ hàm, điều này sẽ không hoạt động.

1
Có vấn đề gì với điều này, @Leeor? Ác cảm chung cho macro? Hoặc bạn không thích thực tế là macro này có thể bị nhầm lẫn với hàm (và thậm chí có thể ẩn nó)?
Palec

1
@Palec, cái sau. Ẩn một định nghĩa lại là một macro có thể dẫn đến rất nhiều nhầm lẫn. Một lập trình viên khác sử dụng mã này nghĩ rằng anh ta đang sử dụng một thứ và vô tình bị buộc phải sử dụng thứ kia. Đó là một quả bom hẹn giờ.
Leeor

1
Sau khi cho nó một suy nghĩ khác, tôi đồng ý rằng đây thực sự là một giải pháp tồi. Trong số những thứ khác tôi tìm thấy một lý do kỹ thuật: Khi sử dụng bzerothông qua các con trỏ hàm, điều này sẽ không hoạt động.
Palec

Bạn thực sự nên đã gọi macro của bạn một cái gì đó khác hơn bzero. Đây là một sự tàn bạo.
Dan Bechard

-2

memset mất 3 tham số, bzero mất 2 trong bộ nhớ bị ràng buộc rằng tham số phụ sẽ mất thêm 4 byte và hầu hết thời gian nó sẽ được sử dụng để đặt mọi thứ thành 0

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.