Malloc vs new - padding khác nhau


110

Tôi đang xem lại mã C ++ của người khác cho dự án của chúng tôi sử dụng MPI để tính toán hiệu suất cao (10 ^ 5 - 10 ^ 6 lõi). Mã này nhằm cho phép liên lạc giữa (có thể) các máy khác nhau trên các kiến ​​trúc khác nhau. Anh ấy đã viết một bình luận có nội dung như sau:

Chúng tôi thường sử dụng newdelete, nhưng ở đây tôi đang sử dụng mallocfree. Điều này là cần thiết vì một số trình biên dịch sẽ đệm dữ liệu khác nhau khi newđược sử dụng, dẫn đến lỗi khi truyền dữ liệu giữa các nền tảng khác nhau. Điều này không xảy ra với malloc.

Điều này không phù hợp với bất cứ điều gì tôi biết từ các câu hỏi tiêu chuẩn newso với malloc.

Sự khác biệt giữa new / delete và malloc / free là gì? gợi ý về ý tưởng rằng trình biên dịch có thể tính toán kích thước của một đối tượng theo cách khác (nhưng tại sao điều đó lại khác với cách sử dụng sizeof?).

malloc & position new so với new là một câu hỏi khá phổ biến nhưng chỉ nói về việc newsử dụng các hàm tạo ở những nơi mallockhông, không liên quan đến điều này.

làm thế nào để malloc hiểu được sự liên kết? nói rằng bộ nhớ được đảm bảo được căn chỉnh đúng với một trong hai newhoặc mallocđó là những gì tôi đã nghĩ trước đây.

Tôi đoán là anh ấy đã chẩn đoán sai lỗi của chính mình trong quá khứ và suy ra điều đó newmallocđưa ra các số lượng đệm khác nhau, điều mà tôi nghĩ có lẽ không đúng. Nhưng tôi không thể tìm thấy câu trả lời với Google hoặc trong bất kỳ câu hỏi nào trước đây.

Giúp tôi, StackOverflow, bạn là hy vọng duy nhất của tôi!


33
+1 cho việc nghiên cứu các chủ đề SO khác nhau một mình!
iammilind

7
+1 Dễ dàng trở thành một trong những công việc nghiên cứu "tự giúp mình trước khi tôi hỏi người khác" tốt nhất mà tôi đã thấy trên SO trong một thời gian DÀI. Ước gì tôi có thể ủng hộ điều này một vài lần nữa.
WhozCraig,

1
Mã chuyển có giả định rằng dữ liệu được căn chỉnh theo bất kỳ cách cụ thể nào, ví dụ: nó bắt đầu ở ranh giới tám byte không? Điều này có thể khác nhau giữa mallocnew, như newtrong một số môi trường phân bổ khối, thêm một số dữ liệu vào đầu và trả lại một con trỏ đến một vị trí ngay sau dữ liệu này. (Tôi đồng ý với những người khác, bên trong khối dữ liệu, mallocnewphải sử dụng cùng một loại đệm.)
Lindydancer

1
Chà, tôi không ngờ câu hỏi này lại phổ biến đến thế! @Lindydancer, tôi không nghĩ rằng bất kỳ ranh giới 8 byte nào được giả định. Điểm thú vị mặc dù.
hcarver

1
Một lý do để sử dụng một phương pháp cấp phát này thay cho một phương thức khác là khi "ai đó khác" đang thực hiện việc phát hành đối tượng. Nếu "ai đó khác" xóa đối tượng bằng cách sử dụng miễn phí, bạn phải cấp phát bằng cách sử dụng malloc. (Vấn đề của pad là một con cá trích đỏ.)
Lindydancer

Câu trả lời:


25

IIRC có một điểm khó hiểu. mallocđược đảm bảo trả về một địa chỉ được căn chỉnh cho bất kỳ loại tiêu chuẩn nào. ::operator new(n)chỉ được đảm bảo trả về một địa chỉ được căn chỉnh cho bất kỳ kiểu chuẩn nào không lớn hơn n và nếu Tkhông phải là một kiểu ký tự thì new T[n]chỉ cần trả lại một địa chỉ được căn chỉnh cho T.

Nhưng điều này chỉ có liên quan khi bạn đang chơi các thủ thuật dành riêng cho việc triển khai như sử dụng một vài bit dưới cùng của con trỏ để lưu trữ cờ hoặc dựa vào địa chỉ để có nhiều liên kết hơn mức nó cần.

Nó không ảnh hưởng đến phần đệm bên trong đối tượng, đối tượng này nhất thiết phải có cùng một bố cục bất kể bạn đã phân bổ bộ nhớ mà nó chiếm như thế nào. Vì vậy, thật khó để biết sự khác biệt có thể dẫn đến lỗi truyền dữ liệu như thế nào.

Có bất kỳ dấu hiệu nào mà tác giả của nhận xét đó nghĩ về các đối tượng trên ngăn xếp hoặc trong hình cầu, cho dù theo ý kiến ​​của anh ta thì chúng "được đệm như malloc" hay "được đệm như mới"? Điều đó có thể cung cấp manh mối về nguồn gốc của ý tưởng.

Có lẽ anh ấy bối rối, nhưng có lẽ mã anh ấy nói về là hơn một sự khác biệt giữa thẳng malloc(sizeof(Foo) * n)vs new Foo[n]. Có lẽ nó giống như sau:

malloc((sizeof(int) + sizeof(char)) * n);

vs.

struct Foo { int a; char b; }
new Foo[n];

Đó là, có thể anh ấy đang nói "Tôi sử dụng malloc", nhưng có nghĩa là "Tôi đóng gói dữ liệu theo cách thủ công vào các vị trí không được đánh dấu thay vì sử dụng cấu trúc". Thực ra malloclà không cần thiết để đóng gói cấu trúc theo cách thủ công, nhưng không nhận ra đó là mức độ ít nhầm lẫn hơn. Nó là cần thiết để xác định bố trí dữ liệu được gửi qua dây. Các triển khai khác nhau sẽ đệm dữ liệu khác nhau khi cấu trúc được sử dụng.


Cảm ơn những điểm về sự liên kết. Dữ liệu được đề cập là một mảng char, vì vậy tôi nghi ngờ nó không phải là thứ liên kết ở đây, cũng không phải là thứ cấu trúc - mặc dù đó cũng là suy nghĩ đầu tiên của tôi.
hcarver

5
@Hbcdev: charcác mảng tốt không bao giờ được đệm ở tất cả, vì vậy tôi sẽ gắn bó với "nhầm lẫn" như lời giải thích.
Steve Jessop

5

Đồng nghiệp của bạn có thể đã nghĩ new[]/delete[]đến cookie ma thuật (đây là thông tin mà triển khai sử dụng khi xóa một mảng). Tuy nhiên, điều này sẽ không thành vấn đề nếu việc phân bổ bắt đầu tại địa chỉ được trả về new[]được sử dụng (trái ngược với của người cấp phát).

Đóng gói có vẻ dễ xảy ra hơn. Các biến thể trong ABI có thể (ví dụ) dẫn đến một số lượng byte theo sau khác nhau được thêm vào cuối cấu trúc (điều này bị ảnh hưởng bởi sự liên kết, cũng hãy xem xét các mảng). Với malloc, vị trí của cấu trúc có thể được chỉ định và do đó dễ dàng di chuyển hơn đến ABI nước ngoài. Những biến thể này thường được ngăn chặn bằng cách chỉ định sự liên kết và đóng gói các cấu trúc chuyển.


2
Đây là điều đầu tiên tôi nghĩ, vấn đề "cấu trúc lớn hơn tổng các phần của nó". Có lẽ đây là nơi khởi nguồn ý tưởng của anh ấy.
hcarver

3

Bố cục của một đối tượng không thể phụ thuộc vào việc nó được cấp phát bằng cách sử dụng mallochoặc new. Cả hai đều trả về cùng một loại con trỏ và khi bạn chuyển con trỏ này cho các hàm khác, chúng sẽ không biết đối tượng được cấp phát như thế nào. sizeof *ptrchỉ phụ thuộc vào khai báo ptrchứ không phải cách nó được chỉ định.


3

Tôi nghĩ bạn đúng. Padding được thực hiện bởi trình biên dịch không newhoặc malloc. Cân nhắc về phần đệm sẽ áp dụng ngay cả khi bạn đã khai báo một mảng hoặc cấu trúc mà không sử dụng newhoặc hoàn malloctoàn. Trong mọi trường hợp, mặc dù tôi có thể thấy cách triển khai khác nhau newmalloccó thể gây ra sự cố khi chuyển mã giữa các nền tảng, tôi hoàn toàn không biết chúng có thể gây ra vấn đề như thế nào khi chuyển dữ liệu giữa các nền tảng.


Trước đây tôi cho rằng bạn có thể coi newlà một người bao bọc tốt mallocnhưng có vẻ như từ các câu trả lời khác, điều đó không hoàn toàn đúng. Sự đồng thuận dường như là phần đệm phải giống nhau; Tôi nghĩ rằng vấn đề với việc chuyển dữ liệu giữa các nền tảng chỉ đến nếu cơ chế chuyển của bạn là thiếu sót :)
hcarver

0

Khi tôi muốn kiểm soát bố cục của cấu trúc dữ liệu cũ thuần túy của mình, tôi sử dụng trình biên dịch MS Visual #pragma pack(1). Tôi cho rằng một chỉ thị trình biên dịch trước như vậy được hỗ trợ cho hầu hết các trình biên dịch, như gcc chẳng hạn .

Điều này có hậu quả là căn chỉnh tất cả các trường của cấu trúc đằng sau cái kia, không có khoảng trống.

Nếu nền tảng ở đầu bên kia cũng làm như vậy (tức là đã biên dịch cấu trúc trao đổi dữ liệu của nó với khoảng đệm là 1), thì dữ liệu được truy xuất ở cả hai phía đều phù hợp. Vì vậy, tôi chưa bao giờ phải chơi với malloc trong C ++.

Tệ nhất là tôi đã xem xét việc nạp chồng toán tử mới để nó thực hiện một số việc phức tạp, thay vì sử dụng malloc trực tiếp trong C ++.


Có những tình huống nào mà bạn muốn kiểm soát bố cục cấu trúc dữ liệu? Chỉ tò mò.
hcarver

Và có ai biết về trình biên dịch hỗ trợ pragma packhoặc tương tự không? Tôi nhận ra nó sẽ không phải là một phần của tiêu chuẩn.
hcarver

gcc hỗ trợ nó chẳng hạn. trong tình huống nào tôi cần điều đó: chia sẻ dữ liệu nhị phân giữa hai dạng đĩa khác nhau: chia sẻ luồng nhị phân giữa windows và palmOS, giữa windows và linux. liên kết về gcc: gcc.gnu.org/onlineocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland

0

Đây là phỏng đoán hoang đường của tôi về nơi mà thứ này đến từ. Như bạn đã đề cập, vấn đề là với việc truyền dữ liệu qua MPI.

Cá nhân tôi, đối với các cấu trúc dữ liệu phức tạp mà tôi muốn gửi / nhận qua MPI, tôi luôn triển khai các phương pháp tuần tự hóa / giải mã hóa để đóng gói / giải nén toàn bộ vào / từ một mảng ký tự. Bây giờ, do padding, chúng tôi biết rằng kích thước của cấu trúc đó có thể lớn hơn kích thước của các thành viên của nó và do đó người ta cũng cần tính toán kích thước không đệm của cấu trúc dữ liệu để chúng ta biết có bao nhiêu byte đang được gửi / nhận.

Ví dụ: nếu bạn muốn gửi / nhận std::vector<Foo> Aqua MPI bằng kỹ thuật đã nói, sẽ sai khi cho rằng kích thước của mảng ký tự kết quả A.size()*sizeof(Foo)nói chung là sai. Nói cách khác, mỗi lớp triển khai các phương thức serialize / deserialize, cũng nên triển khai một phương thức báo cáo kích thước của mảng (hoặc tốt hơn là lưu trữ mảng trong một vùng chứa). Điều này có thể trở thành lý do đằng sau một lỗi. Tuy nhiên, bằng cách này hay cách khác, điều đó không liên quan gì đến newvs mallocnhư đã chỉ ra trong chủ đề này.


Việc sao chép vào các mảng char có thể là một vấn đề - có thể một số lõi của bạn nằm trên kiến ​​trúc little-endian và một số big-endian (có thể không, nhưng có thể). Bạn sẽ phải mã hóa chúng bằng XDR hoặc gì đó, nhưng bạn chỉ có thể sử dụng các kiểu dữ liệu MPI do người dùng xác định. Họ dễ dàng tính đến phần đệm. Nhưng tôi có thể thấy những gì bạn đang nói về nguyên nhân có thể gây ra hiểu lầm - đó là điều tôi gọi là vấn đề "cấu trúc lớn hơn tổng các phần của nó".
hcarver

Có, xác định kiểu dữ liệu MPI là một cách khác / đúng để thực hiện việc này. Điểm tốt về độ bền. Mặc dù, tôi thực sự nghi ngờ điều đó sẽ xảy ra trên các cụm thực tế. Dù sao, tôi nghĩ nếu họ làm theo cùng một chiến lược, điều này có thể dẫn đến lỗi ...
mmirzadeh

0

Trong c ++: new từ khóa được sử dụng để cấp phát một số byte bộ nhớ cụ thể liên quan đến một số cấu trúc dữ liệu. Ví dụ, bạn đã xác định một số lớp hoặc cấu trúc và bạn muốn cấp phát bộ nhớ cho đối tượng của nó.

myclass *my = new myclass();

hoặc là

int *i = new int(2);

Nhưng trong mọi trường hợp, bạn cần có kiểu dữ liệu đã xác định (lớp, struct, union, int, char, v.v.) và chỉ byte bộ nhớ đó sẽ được cấp phát cần thiết cho đối tượng / biến của nó. (tức là; bội số của kiểu dữ liệu đó).

Nhưng trong trường hợp của phương thức malloc (), bạn có thể cấp phát bất kỳ byte bộ nhớ nào và bạn không cần chỉ định kiểu dữ liệu mọi lúc. Ở đây bạn có thể quan sát nó trong một vài khả năng của malloc ():

void *v = malloc(23);

hoặc là

void *x = malloc(sizeof(int) * 23);

hoặc là

char *c = (char*)malloc(sizeof(char)*35);

-1

malloc là một loại hàm và new là một loại dữ liệu trong c ++ trong c ++, nếu chúng ta sử dụng malloc hơn chúng ta phải và nên sử dụng typecast nếu không trình biên dịch sẽ cho bạn lỗi và nếu chúng ta sử dụng kiểu dữ liệu mới để cấp phát bộ nhớ thì chúng ta không cần đánh máy


1
Tôi nghĩ bạn nên cố gắng tranh luận câu trả lời của mình nhiều hơn một chút.
Carlo

Điều này dường như không giải quyết được câu hỏi về việc họ làm những việc khác nhau với paddings, đó là những gì tôi thực sự đã hỏi ở trên.
hcarver
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.