Tại sao phân bổ ban đầu của C ++ lớn hơn nhiều so với C?


138

Khi sử dụng cùng một mã, chỉ cần thay đổi trình biên dịch (từ trình biên dịch C sang trình biên dịch C ++) sẽ thay đổi lượng bộ nhớ được phân bổ. Tôi không chắc tại sao lại như vậy và muốn hiểu nó nhiều hơn. Cho đến nay, phản hồi tốt nhất mà tôi nhận được là "có thể là luồng I / O", không mô tả và khiến tôi tự hỏi về khía cạnh "bạn không trả tiền cho những gì bạn không sử dụng" của C ++.

Tôi đang sử dụng trình biên dịch Clang và GCC, phiên bản 7.0.1-8 và 8.3.0-6 tương ứng. Hệ thống của tôi đang chạy trên Debian 10 (Buster), mới nhất. Các điểm chuẩn được thực hiện thông qua Valgrind Massif.

#include <stdio.h>

int main() {
    printf("Hello, world!\n");
    return 0;
}

Mã được sử dụng không thay đổi, nhưng cho dù tôi biên dịch thành C hay C ++, nó sẽ thay đổi kết quả của điểm chuẩn Valgrind. Các giá trị vẫn nhất quán trên các trình biên dịch, tuy nhiên. Việc phân bổ thời gian chạy (cao điểm) cho chương trình diễn ra như sau:

  • GCC (C): 1.032 byte (1 KB)
  • G ++ (C ++): 73.744 byte, (~ 74 KB)
  • Clang (C): 1.032 byte (1 KB)
  • Clang ++ (C ++): 73.744 byte (~ 74 KB)

Để biên dịch, tôi sử dụng các lệnh sau:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

Đối với Valgrind, tôi chạy valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langtrên từng trình biên dịch và ngôn ngữ, sau đó ms_printđể hiển thị các đỉnh.

Tôi đang làm gì đó sai ở đây?


11
Để bắt đầu, như thế nào được bạn xây dựng? Bạn sử dụng tùy chọn nào? Và làm thế nào để bạn đo lường? Làm thế nào để bạn chạy Valgrind?
Một số lập trình viên anh chàng

17
Nếu tôi nhớ chính xác, các trình biên dịch C ++ hiện đại phải có một mô hình ngoại lệ, trong đó không có hiệu năng nào để vào một trykhối với chi phí của một bộ nhớ lớn hơn, có thể bằng một bảng nhảy hoặc một cái gì đó. Có thể thử biên dịch mà không có ngoại lệ và xem những gì có tác động. Chỉnh sửa: Trên thực tế, lặp đi lặp lại thử vô hiệu hóa các tính năng c ++ khác nhau để xem tác động nào đến dấu chân bộ nhớ.
François Andrieux

3
Khi biên dịch clang++ -xcthay vì clang, phân bổ tương tự đã có, điều này cho thấy mạnh mẽ là do các thư viện được liên kết
Justin

14
@bigwillydos Đây thực sự là C ++, tôi không thấy bất kỳ phần nào trong thông số kỹ thuật của C ++ mà nó phá vỡ ... Khác với khả năng bao gồm stdio.h thay vì cstdio nhưng điều này được cho phép ít nhất là trong phiên bản C ++ cũ hơn. Bạn nghĩ gì là "không đúng" trong chương trình này?
Vality

4
Tôi thấy nghi ngờ rằng các trình biên dịch gcc và clang tạo ra cùng một số byte Cchính xác trong chế độ và cùng một số lượng chính xác của C++chế độ byte . Bạn đã thực hiện một lỗi sao chép?
RonJohn

Câu trả lời:


149

Việc sử dụng heap đến từ thư viện chuẩn C ++. Nó phân bổ bộ nhớ để sử dụng thư viện nội bộ khi khởi động. Nếu bạn không liên kết với nó, sẽ không có sự khác biệt giữa phiên bản C và C ++. Với GCC và Clang, bạn có thể biên dịch tệp bằng:

g ++ -Wl, - khi cần main.cpp

Điều này sẽ hướng dẫn trình liên kết không liên kết với các thư viện không sử dụng. Trong mã ví dụ của bạn, thư viện C ++ không được sử dụng, do đó không nên liên kết với thư viện chuẩn C ++.

Bạn cũng có thể kiểm tra điều này với tệp C. Nếu bạn biên dịch với:

gcc main.c -lstdc ++

Việc sử dụng heap sẽ xuất hiện lại, mặc dù bạn đã xây dựng chương trình C.

Việc sử dụng heap rõ ràng phụ thuộc vào việc triển khai thư viện C ++ cụ thể mà bạn đang sử dụng. Trong trường hợp của bạn, đó là thư viện GNU C ++, libstdc ++ . Các triển khai khác có thể không phân bổ cùng một lượng bộ nhớ hoặc chúng có thể không phân bổ bất kỳ bộ nhớ nào (ít nhất là không khởi động.) Thư viện LLVM C ++ ( libc ++ ) chẳng hạn không phân bổ heap khi khởi động, ít nhất là trên Linux của tôi máy móc:

clang ++ -stdlib = libc ++ main.cpp

Việc sử dụng heap cũng giống như không liên kết với nó.

(Nếu quá trình biên dịch thất bại, thì libc ++ có thể chưa được cài đặt. Tên gói thường chứa "libc ++" hoặc "libcxx".)


50
Khi nhìn thấy câu trả lời này, suy nghĩ đầu tiên của tôi là: " Nếu lá cờ này giúp giảm chi phí không cần thiết, tại sao nó không được bật theo mặc định? ". Có một câu trả lời tốt cho điều đó?
Nat

4
@Nat Tôi đoán là tại thời điểm dev thì việc biên dịch chậm hơn. Khi bạn đã sẵn sàng để tạo một bản phát hành, bạn sẽ bật nó lên. Ngoài ra trong một cơ sở mã thông thường / lớn, sự khác biệt có thể là tối thiểu (nếu bạn đang sử dụng nhiều thư viện STD, v.v.)
DarcyThomas

24
@Nat -Wl,--as-neededCờ xóa các thư viện mà bạn chỉ định trong các -lcờ của mình nhưng bạn không thực sự sử dụng. Vì vậy, nếu bạn không sử dụng thư viện, thì đừng liên kết với nó. Bạn không cần cờ này cho việc này. Tuy nhiên, nếu hệ thống xây dựng của bạn thêm quá nhiều thư viện và sẽ có rất nhiều công việc để dọn sạch tất cả chúng và chỉ liên kết những thứ cần thiết, thì bạn có thể sử dụng cờ này để thay thế. Thư viện tiêu chuẩn là một ngoại lệ, vì nó tự động được liên kết với. Vì vậy, đây là một trường hợp góc.
Nikos C.

36
@Nat - cần thiết có thể có các tác dụng phụ không mong muốn, nó hoạt động bằng cách kiểm tra xem bạn có sử dụng bất kỳ biểu tượng nào của thư viện không và loại bỏ những thứ không đạt yêu cầu. NHƯNG: một thư viện cũng có thể thực hiện nhiều thứ khác nhau, ví dụ, nếu bạn có một cá thể C ++ tĩnh trong thư viện thì hàm tạo của nó sẽ được gọi tự động. Có những trường hợp hiếm hoi mà một thư viện mà bạn không gọi rõ ràng là cần thiết, nhưng chúng tồn tại.
Norbert Lange

3
@NikosC. Buildsystems không tự động biết biểu tượng nào mà ứng dụng của bạn sử dụng và thư viện nào triển khai chúng (khác nhau giữa các trình biên dịch, vòm, phân phối và thư viện c / c ++). Làm cho đúng là khá rắc rối, ít nhất cho các thư viện thời gian chạy cơ sở. Nhưng đối với những trường hợp hiếm hoi bạn cần một thư viện, bạn chỉ cần sử dụng - không cần thiết cho thư viện đó và để lại - cần thiết ở mọi nơi khác. Một usecase tôi thấy là các thư viện để theo dõi / gỡ lỗi (lttng) và các thư viện thực hiện một số thứ thuộc loại xác thực / kết nối.
Norbert Lange

16

Cả GCC và Clang đều không phải là trình biên dịch - chúng thực sự là các chương trình trình điều khiển toolchain. Điều đó có nghĩa là họ gọi trình biên dịch, trình biên dịch và trình liên kết.

Nếu bạn biên dịch mã của mình bằng trình biên dịch C hoặc C ++, bạn sẽ nhận được cùng một cụm được sản xuất. Trình biên dịch sẽ tạo ra các đối tượng tương tự. Sự khác biệt là trình điều khiển chuỗi công cụ sẽ cung cấp đầu vào khác nhau cho trình liên kết cho hai ngôn ngữ khác nhau: các phần khởi động khác nhau (C ++ yêu cầu mã để thực thi các hàm tạo và hàm hủy cho các đối tượng có thời lượng lưu trữ tĩnh hoặc luồng cục bộ ở cấp không gian tên và yêu cầu cơ sở hạ tầng để xếp chồng các khung để hỗ trợ giải nén trong quá trình xử lý ngoại lệ, ví dụ), thư viện chuẩn C ++ (cũng có các đối tượng có thời lượng lưu trữ tĩnh ở mức không gian tên) và có thể là các thư viện thời gian chạy bổ sung (ví dụ: libgcc với cơ sở hạ tầng ngăn xếp ngăn xếp).

Nói tóm lại, nó không phải là trình biên dịch gây ra sự gia tăng dấu chân, đó là liên kết trong những thứ bạn đã chọn sử dụng bằng cách chọn ngôn ngữ C ++.

Đúng là C ++ có triết lý "chỉ trả tiền cho những gì bạn sử dụng", nhưng bằng cách sử dụng ngôn ngữ, bạn phải trả tiền cho nó. Bạn có thể vô hiệu hóa các phần của ngôn ngữ (RTTI, xử lý ngoại lệ) nhưng sau đó bạn không sử dụng C ++ nữa. Như đã đề cập trong một câu trả lời khác, nếu bạn hoàn toàn không sử dụng thư viện tiêu chuẩn, bạn có thể hướng dẫn người lái xe rời khỏi đó (--Wl, - khi cần thiết) nhưng nếu bạn sẽ không sử dụng bất kỳ tính năng nào của C ++ hoặc thư viện của nó, tại sao bạn thậm chí chọn C ++ làm ngôn ngữ lập trình?


Thực tế là cho phép xử lý ngoại lệ có chi phí ngay cả khi bạn không thực sự sử dụng nó là một vấn đề. Điều đó không bình thường đối với các tính năng của C ++ nói chung và đó là điều mà các nhóm làm việc theo tiêu chuẩn C ++ đang cố gắng nghĩ cách khắc phục. Xem bài phát biểu quan trọng của Herb Sutter tại ACCU 2019 Khử phân mảnh C ++: Làm cho các ngoại lệ trở nên hợp lý hơn và có thể sử dụng được . Đó là một thực tế không may, mặc dù, trong C ++ hiện tại. Và các ngoại lệ C ++ truyền thống có thể sẽ luôn có chi phí đó, ngay cả khi / khi các cơ chế mới cho các ngoại lệ mới được thêm vào bằng một từ khóa.
Peter Cordes
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.