Trong C ++, tôi có trả tiền cho những gì tôi không ăn không?


170

Hãy xem xét các ví dụ thế giới xin chào sau trong C và C ++:

main.c

#include <stdio.h>

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

main.cpp

#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}

Khi tôi biên dịch chúng trong godbolt để lắp ráp, kích thước của mã C chỉ là 9 dòng ( gcc -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

Nhưng kích thước của mã C ++ là 22 dòng ( g++ -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

... Nó lớn hơn nhiều.

Điều nổi tiếng là trong C ++, bạn phải trả cho những gì bạn ăn. Vì vậy, trong trường hợp này, tôi đang trả tiền cho cái gì?


3
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew


26
Chưa bao giờ nghe thuật ngữ eatliên quan đến C ++. Tôi tin rằng bạn có nghĩa là: "Bạn chỉ trả tiền cho những gì bạn sử dụng "?
Giacomo Alzetta

7
@GiacomoAlzetta, ... đó là một chủ nghĩa thông tục, áp dụng khái niệm về một bữa tiệc buffet hoàn toàn có thể ăn. Sử dụng thuật ngữ chính xác hơn chắc chắn là thích hợp hơn với khán giả toàn cầu, nhưng là một người nói tiếng Anh Mỹ bản địa, tiêu đề có ý nghĩa với tôi.
Charles Duffy

5
@ trcar813 Rò rỉ bộ nhớ không liên quan gì đến câu trích dẫn và câu hỏi OP. Quan điểm "Bạn chỉ trả tiền cho những gì bạn sử dụng" / "Bạn không trả tiền cho những gì bạn không sử dụng" là nói rằng không có thành tích nào được thực hiện nếu bạn không sử dụng một tính năng / trừu tượng cụ thể. Rò rỉ bộ nhớ không liên quan gì đến điều này cả, và điều này chỉ cho thấy thuật ngữ eatnày mơ hồ hơn và nên tránh.
Giacomo Alzetta

Câu trả lời:


60

Những gì bạn đang trả tiền là gọi một thư viện nặng (không nặng như in vào bàn điều khiển). Bạn khởi tạo một ostreamđối tượng. Có một số lưu trữ ẩn. Sau đó, bạn gọi std::endlđó không phải là từ đồng nghĩa \n. Các iostreamthư viện sẽ giúp bạn điều chỉnh nhiều thiết lập và đặt gánh nặng lên bộ vi xử lý chứ không phải là lập trình viên. Đây là những gì bạn đang trả tiền cho.

Hãy xem lại mã:

.LC0:
        .string "Hello world"
main:

Đang khởi tạo một đối tượng Ostream + cout

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

Gọi coutlại để in một dòng mới và tuôn ra

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Khởi tạo lưu trữ tĩnh:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

Ngoài ra, điều cần thiết là phải phân biệt giữa ngôn ngữ và thư viện.

BTW, đây chỉ là một phần của câu chuyện. Bạn không biết những gì được viết trong các chức năng bạn đang gọi.


5
Là một lưu ý bổ sung, kiểm tra kỹ lưỡng sẽ cho thấy rằng việc chuẩn bị một chương trình C ++ với "ios_base :: sync_with_stdio (false);" và "cin.tie (NULL);" sẽ làm cho cout nhanh hơn printf (Printf có định dạng chuỗi trên cao). Cái đầu tiên loại bỏ chi phí từ việc đảm bảo cout; printf; coutghi theo thứ tự (Vì chúng có bộ đệm riêng). Thứ hai sẽ không đồng bộ hóa coutcin, cout; cincó khả năng yêu cầu người dùng cung cấp thông tin trước. Flushing sẽ buộc nó chỉ đồng bộ hóa khi bạn thực sự cần nó.
Nicholas Pipitone

Xin chào Nicholas, cảm ơn bạn rất nhiều vì đã thêm những ghi chú hữu ích này.
Bắt đầu

"Điều cần thiết là phải phân biệt ngôn ngữ và thư viện": Vâng, nhưng thư viện tiêu chuẩn đi kèm với ngôn ngữ là ngôn ngữ duy nhất có sẵn ở mọi nơi, vì vậy nó là thư viện được sử dụng ở mọi nơi (và vâng, thư viện chuẩn C là một phần của đặc tả C ++, vì vậy nó có thể được sử dụng khi muốn). Như "Bạn không biết những gì được viết trong các chức năng bạn đang gọi": Bạn có thể liên kết tĩnh nếu bạn thực sự muốn biết, và thực sự mã gọi mà bạn kiểm tra có thể không liên quan.
Peter - Phục hồi Monica

211

Vì vậy, trong trường hợp này, tôi đang trả tiền cho cái gì?

std::coutmạnh hơn và phức tạp hơn printf. Nó hỗ trợ những thứ như địa phương, cờ định dạng trạng thái, v.v.

Nếu bạn không cần những thứ đó, hãy sử dụng std::printfhoặc std::puts- chúng có sẵn <cstdio>.


Điều nổi tiếng là trong C ++, bạn phải trả cho những gì bạn ăn.

Tôi cũng muốn làm rõ rằng C ++ ! = Thư viện chuẩn C ++. Thư viện tiêu chuẩn được cho là có mục đích chung và "đủ nhanh", nhưng nó thường sẽ chậm hơn so với việc triển khai chuyên biệt những gì bạn cần.

Mặt khác, ngôn ngữ C ++ cố gắng để có thể viết mã mà không phải trả thêm chi phí ẩn không cần thiết (ví dụ: chọn tham gia virtual, không thu gom rác).


4
+1 để nói Thư viện chuẩn được cho là mục đích chung và "đủ nhanh", nhưng nó thường sẽ chậm hơn so với việc triển khai chuyên biệt những gì bạn cần. Nhiều người dường như hoàn toàn sử dụng các thành phần STL mà không xem xét ý nghĩa hiệu suất so với việc tự lăn.
Craig Estey

7
@Craig OTOH nhiều phần của thư viện tiêu chuẩn thường nhanh hơn và đúng hơn so với những gì người ta thường có thể sản xuất thay thế.
Peter - Phục hồi Monica

2
@ PeterA.Schneider OTOH, khi phiên bản STL chậm hơn 20x-30x, việc tự lăn bánh là điều tốt. Xem câu trả lời của tôi ở đây: codereview.stackexchange.com/questions/191747/ Khăn ở đó, những người khác cũng đề nghị [ít nhất là một phần] cuộn của riêng bạn.
Craig Estey

1
@CraigEstey Một vectơ là (ngoài phân bổ động ban đầu có thể có ý nghĩa, tùy thuộc vào số lượng công việc sẽ được thực hiện cuối cùng với một thể hiện cụ thể) không kém hiệu quả hơn một mảng C; nó được thiết kế để không Phải cẩn thận không sao chép nó xung quanh, dành đủ không gian ban đầu, v.v., nhưng tất cả những gì phải được thực hiện với một mảng là tốt, và ít an toàn hơn. Đối với ví dụ được liên kết của bạn: Có, một vectơ vectơ sẽ (trừ khi được tối ưu hóa) sẽ phát sinh thêm một sự gián tiếp so với mảng 2D, nhưng tôi cho rằng hiệu quả 20x không bắt nguồn từ đó mà là thuật toán.
Peter - Tái lập Monica

174

Bạn không so sánh C và C ++. Bạn đang so sánh printfstd::cout, có khả năng cho những thứ khác nhau (địa phương, định dạng trạng thái, v.v.).

Hãy thử sử dụng mã sau đây để so sánh. Godbolt tạo ra hội đồng giống nhau cho cả hai tệp (được thử nghiệm với gcc 8.2, -O3).

C chính:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

chính.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}


Chúc mừng cho hiển thị mã tương đương và giải thích lý do.
HackSlash

134

Danh sách của bạn thực sự đang so sánh táo và cam, nhưng không phải vì lý do ngụ ý trong hầu hết các câu trả lời khác.

Hãy kiểm tra xem mã của bạn thực sự làm gì:

C:

  • in một chuỗi "Hello world\n"

C ++:

  • truyền chuỗi "Hello world"vàostd::cout
  • truyền các std::endlthao tác vàostd::cout

Rõ ràng mã C ++ của bạn đang làm việc gấp đôi. Để so sánh công bằng, chúng ta nên kết hợp điều này:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

Thật bất ngờ và mã lắp ráp của bạn maintrông rất giống với C:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

Trên thực tế, chúng ta có thể so sánh dòng mã C và C ++ theo từng dòng và có rất ít sự khác biệt :

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

Sự khác biệt thực sự duy nhất là trong C ++, chúng ta gọi operator <<với hai đối số ( std::coutvà chuỗi). Chúng ta có thể loại bỏ ngay cả sự khác biệt nhỏ đó bằng cách sử dụng C eqivalent gần hơn : fprintf, cũng có một đối số đầu tiên chỉ định luồng.

Điều này để lại mã lắp ráp cho _GLOBAL__sub_I_main, được tạo cho C ++ nhưng không phải C. Đây là chi phí thực sự duy nhất có thể nhìn thấy trong danh sách lắp ráp này (có thêm chi phí vô hình cho cả hai dĩ nhiên, ngôn ngữ). Mã này thực hiện thiết lập một lần một số chức năng thư viện chuẩn C ++ khi bắt đầu chương trình C ++.

Nhưng, như đã giải thích trong các câu trả lời khác, sự khác biệt có liên quan giữa hai chương trình này sẽ không được tìm thấy trong đầu ra lắp ráp của mainchức năng vì tất cả các công việc nặng nề xảy ra đằng sau hậu trường.


21
Ngẫu nhiên thời gian chạy C cũng cần phải được thiết lập và điều này xảy ra trong một hàm được gọi _startnhưng mã của nó là một phần của thư viện thời gian chạy C. Ở bất cứ giá nào, điều này xảy ra cho cả C và C ++.
Konrad Rudolph

2
@Ded repeatator: Trên thực tế, theo mặc định, thư viện iostream không thực hiện bất kỳ bộ đệm nàostd::cout và thay vào đó chuyển I / O sang triển khai stdio (sử dụng cơ chế đệm của chính nó). Đặc biệt, khi được kết nối với (cái được gọi là) một thiết bị đầu cuối tương tác, thì theo mặc định, bạn sẽ không bao giờ thấy đầu ra được đệm hoàn toàn khi ghi vào std::cout. Bạn phải vô hiệu hóa đồng bộ hóa với stdio nếu bạn muốn thư viện iostream sử dụng các cơ chế đệm của riêng mình std::cout.

6
@KonradRudolph: Thật ra, printfkhông cần phải tuôn dòng ở đây. Trong thực tế, trong trường hợp sử dụng phổ biến (đầu ra được chuyển hướng đến tệp), bạn thường sẽ thấy printfcâu lệnh đó không tuôn ra. Chỉ khi đầu ra được đệm hoặc không có bộ đệm thì printfkích hoạt mới được kích hoạt.

2
@PeterCordes: Phải, bạn không thể chặn với bộ đệm đầu ra không được quét, nhưng bạn có thể gặp bất ngờ khi chương trình đã chấp nhận đầu vào của bạn và tiếp tục mà không hiển thị đầu ra dự kiến. Tôi biết điều này bởi vì tôi đã có dịp gỡ lỗi "Trợ giúp, chương trình của tôi bị treo trong khi nhập nhưng tôi không thể hiểu tại sao!" đã cho một nhà phát triển khác phù hợp trong một vài ngày.

2
@PeterCordes: Đối số tôi đưa ra là "viết những gì bạn muốn nói" - dòng mới phù hợp khi bạn có nghĩa là đầu ra sẽ có sẵn và endl phù hợp khi bạn có nghĩa là đầu ra sẽ có sẵn ngay lập tức.

53

Điều nổi tiếng là trong C ++, bạn phải trả cho những gì bạn ăn. Vì vậy, trong trường hợp này, tôi đang trả tiền cho cái gì?

Điều đó thật đơn giản. Bạn trả tiền cho std::cout. "Bạn chỉ trả tiền cho những gì bạn ăn" không có nghĩa là "bạn luôn nhận được giá tốt nhất". Chắc chắn, printflà rẻ hơn. Người ta có thể lập luận rằng std::coutan toàn hơn và linh hoạt hơn, do đó chi phí lớn hơn của nó là hợp lý (chi phí cao hơn, nhưng cung cấp nhiều giá trị hơn), nhưng điều đó bỏ lỡ điểm. Bạn không sử dụng printf, bạn sử dụng std::cout, vì vậy bạn trả tiền cho việc sử dụng std::cout. Bạn không trả tiền cho việc sử dụng printf.

Một ví dụ điển hình là các hàm ảo. Các hàm ảo có một số yêu cầu về không gian và chi phí thời gian chạy - nhưng chỉ khi bạn thực sự sử dụng chúng. Nếu bạn không sử dụng các chức năng ảo, bạn sẽ không phải trả bất cứ điều gì.

Một vài nhận xét

  1. Ngay cả khi mã C ++ đánh giá các hướng dẫn lắp ráp nhiều hơn, nó vẫn là một số ít các hướng dẫn và bất kỳ chi phí hiệu năng nào vẫn có thể bị lấn át bởi các hoạt động I / O thực tế.

  2. Trên thực tế, đôi khi nó còn tốt hơn "trong C ++, bạn phải trả cho những gì bạn ăn". Ví dụ, trình biên dịch có thể suy ra rằng cuộc gọi hàm ảo không cần thiết trong một số trường hợp và chuyển nó thành cuộc gọi không ảo. Điều đó có nghĩa là bạn có thể nhận được các chức năng ảo miễn phí . Điều đó thật tuyệt phải không?


6
Bạn không nhận được các chức năng ảo miễn phí. Bạn vẫn phải trả chi phí cho lần đầu tiên viết chúng, và sau đó gỡ lỗi chuyển đổi mã của trình biên dịch khi nó không khớp với ý tưởng của bạn về những gì nó phải làm.
alephzero

2
@alephzero Tôi không chắc nó có liên quan đặc biệt để so sánh chi phí phát triển với chi phí hiệu suất.

Thật là một cơ hội tuyệt vời cho một trò chơi chữ bị lãng phí ... Bạn có thể đã sử dụng từ 'calo' thay vì 'giá'. Từ đó bạn có thể nói rằng C ++ béo hơn C. Hoặc ít nhất là ... mã cụ thể được đề cập (Tôi thiên vị so với C ++ có lợi cho C nên tôi không thể vượt qua). Than ôi. @Bilkokuya Nó có thể không liên quan trong mọi trường hợp nhưng chắc chắn đó là thứ người ta không nên coi thường. Do đó, nó có liên quan trên toàn bộ.
Pryftan

46

"Danh sách lắp ráp cho printf" KHÔNG dành cho printf, nhưng dành cho put (loại tối ưu hóa trình biên dịch?); printf phức tạp hơn nhiều so với đặt ... đừng quên!


13
Đây là câu trả lời tốt nhất cho đến nay, vì tất cả những người khác bị treo lên trên một cá trích đỏ về std::coutnội bộ của chúng, không thể nhìn thấy trong danh sách lắp ráp.
Konrad Rudolph

12
Danh sách lắp ráp dành cho một cuộc gọi đến puts , trông giống hệt với một cuộc gọi đến printfnếu bạn chỉ truyền một chuỗi định dạng duy nhất và không có thêm đối số. (ngoại trừ cũng sẽ có một xor %eax,%eaxvì chúng ta chuyển zero FP đối số trong các thanh ghi cho một hàm matrixdic.) Cả hai đều là cách thực hiện, chỉ chuyển một con trỏ tới một chuỗi cho hàm thư viện. Nhưng vâng, tối ưu hóa printfđể putslà một cái gì đó gcc làm cho các định dạng mà chỉ có "%s", hoặc khi không có chuyển đổi, và chuỗi kết thúc bằng một dòng mới.
Peter Cordes

45

Tôi thấy một số câu trả lời hợp lệ ở đây, nhưng tôi sẽ tìm hiểu thêm một chút về chi tiết.

Chuyển đến phần tóm tắt dưới đây để biết câu trả lời cho câu hỏi chính của bạn nếu bạn không muốn đi qua toàn bộ bức tường văn bản này.


Trừu tượng

Vì vậy, trong trường hợp này, tôi đang trả tiền cho cái gì?

Bạn đang trả tiền cho sự trừu tượng . Có thể viết mã đơn giản và thân thiện hơn với con người phải trả giá. Trong C ++, một ngôn ngữ hướng đối tượng, hầu hết mọi thứ đều là đối tượng. Khi bạn sử dụng bất kỳ đối tượng nào, ba điều chính sẽ luôn xảy ra dưới mui xe:

  1. Tạo đối tượng, về cơ bản cấp phát bộ nhớ cho chính đối tượng và dữ liệu của nó.
  2. Khởi tạo đối tượng (thường thông qua một số init()phương thức). Thông thường phân bổ bộ nhớ xảy ra dưới mui xe là điều đầu tiên trong bước này.
  3. Phá hủy đối tượng (không phải luôn luôn).

Bạn không nhìn thấy nó trong mã, nhưng mỗi lần bạn sử dụng một đối tượng, cả ba điều trên đều cần phải xảy ra bằng cách nào đó. Nếu bạn làm mọi thứ bằng tay, mã rõ ràng sẽ dài hơn.

Bây giờ, việc trừu tượng hóa có thể được thực hiện một cách hiệu quả mà không cần thêm chi phí: phương pháp nội tuyến và các kỹ thuật khác có thể được sử dụng bởi cả trình biên dịch và lập trình viên để loại bỏ các chi phí trừu tượng, nhưng đây không phải là trường hợp của bạn.

Điều gì thực sự xảy ra trong C ++?

Đây rồi, chia nhỏ:

  1. Các std::ios_baselớp được khởi tạo, đó là lớp cơ sở cho tất cả mọi thứ I / O có liên quan.
  2. Đối std::couttượng được khởi tạo.
  3. Chuỗi của bạn được tải và chuyển đến std::__ostream_insert, mà (như bạn đã tìm ra bằng tên) là một phương thức std::cout(về cơ bản là <<toán tử) để thêm một chuỗi vào luồng.
  4. cout::endlcũng được truyền cho std::__ostream_insert.
  5. __std_dso_handleđược chuyển đến __cxa_atexit, đây là chức năng toàn cầu chịu trách nhiệm "dọn dẹp" trước khi thoát khỏi chương trình. __std_dso_handlechính nó được gọi bởi chức năng này để phân bổ và tiêu diệt các đối tượng toàn cầu còn lại.

Vậy sử dụng C == không trả tiền cho bất cứ điều gì?

Trong mã C, rất ít bước đang diễn ra:

  1. Chuỗi của bạn được tải và chuyển putsqua thông qua thanh edighi.
  2. puts được gọi.

Không có đối tượng ở bất cứ đâu, do đó không cần phải khởi tạo / phá hủy bất cứ thứ gì.

Tuy nhiên điều này không có nghĩa là bạn không phải "trả tiền" cho bất cứ điều gì trong C . Bạn vẫn đang trả tiền cho sự trừu tượng hóa, đồng thời khởi tạo thư viện chuẩn C và độ phân giải động của printfhàm (hoặc, thực tế puts, được trình biên dịch tối ưu hóa do bạn không cần bất kỳ chuỗi định dạng nào) vẫn xảy ra trong trình duyệt.

Nếu bạn viết chương trình này trong tập hợp thuần túy, nó sẽ trông giống như thế này:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

Mà về cơ bản chỉ dẫn cách gọi các write syscall tiếp theo là exitsyscall. Bây giờ đây sẽ là mức tối thiểu để hoàn thành điều tương tự.


Tóm tắt

C là cách đơn giản hơn và chỉ thực hiện mức tối thiểu cần thiết, để lại toàn quyền kiểm soát cho người dùng, có thể tối ưu hóa hoàn toàn và tùy chỉnh về cơ bản mọi thứ họ muốn. Bạn bảo bộ xử lý tải một chuỗi trong một thanh ghi và sau đó gọi hàm thư viện để sử dụng chuỗi đó. Mặt khác, C ++ phức tạp và trừu tượng hơn nhiều . Điều này có lợi thế rất lớn khi viết mã phức tạp, và cho phép dễ viết hơn và mã thân thiện với con người hơn, nhưng rõ ràng nó có chi phí. Luôn luôn có một nhược điểm về hiệu suất trong C ++ nếu so với C trong các trường hợp như thế này, vì C ++ cung cấp nhiều hơn những gì cần thiết để hoàn thành các nhiệm vụ cơ bản như vậy, và do đó nó tăng thêm chi phí .

Trả lời câu hỏi chính của bạn :

Tôi đang trả tiền cho những gì tôi không ăn?

Trong trường hợp cụ thể này, . Bạn không tận dụng bất cứ thứ gì mà C ++ phải cung cấp nhiều hơn C, nhưng đó chỉ là vì không có gì trong đoạn mã đơn giản mà C ++ có thể giúp bạn: thật đơn giản đến nỗi bạn thực sự không cần C ++.


Ồ, và chỉ một điều nữa!

Những ưu điểm của C ++ có thể trông không rõ ràng ngay từ cái nhìn đầu tiên, vì bạn đã viết một chương trình rất đơn giản và nhỏ, nhưng nhìn vào một ví dụ phức tạp hơn một chút và thấy sự khác biệt (cả hai chương trình đều thực hiện cùng một điều chính xác):

C :

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C ++ :

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

Hy vọng bạn có thể thấy rõ những gì tôi muốn nói ở đây. Cũng lưu ý rằng trong C bạn phải quản lý bộ nhớ ở mức thấp hơn bằng cách sử dụng mallocfreecách bạn cần cẩn thận hơn về lập chỉ mục và kích thước, và cách bạn cần phải rất cụ thể khi lấy đầu vào và in.


27

Có một vài quan niệm sai lầm để bắt đầu. Đầu tiên, chương trình C ++ không dẫn đến 22 hướng dẫn, nó giống như 22.000 trong số đó (tôi đã lấy số đó từ mũ của mình, nhưng nó nằm trong sân bóng). Ngoài ra, mã C cũng không dẫn đến 9 hướng dẫn. Đó chỉ là những người bạn nhìn thấy.

Mã C làm gì là, sau khi thực hiện nhiều thứ mà bạn không thấy, nó gọi một hàm từ CRT (thường không nhất thiết phải xuất hiện dưới dạng lib chung), sau đó không kiểm tra giá trị trả về hoặc xử lý lỗi, và bảo lãnh ra. Tùy thuộc vào trình biên dịch và cài đặt tối ưu hóa, nó thậm chí không thực sự gọi printfnhưng puts, hoặc một cái gì đó thậm chí còn nguyên thủy hơn.
Bạn cũng có thể đã viết ít nhiều cùng một chương trình (ngoại trừ một số hàm init vô hình) trong C ++, nếu chỉ bạn gọi hàm đó cùng một cách. Hoặc, nếu bạn muốn siêu chính xác, cùng chức năng đó có tiền tố std::.

Mã C ++ tương ứng trong thực tế không hoàn toàn giống nhau. Mặc dù toàn bộ <iostream>nó nổi tiếng là một con lợn xấu xí béo ú, có thêm một khoản chi phí khổng lồ cho các chương trình nhỏ (trong một chương trình "thực tế" mà bạn không thực sự chú ý đến vậy), một cách giải thích công bằng hơn là nó rất tệ nhiều thứ bạn không thấy và chỉ hoạt động . Bao gồm nhưng không giới hạn ở định dạng ma thuật của hầu hết mọi thứ hỗn độn, bao gồm các định dạng số và địa phương khác nhau và chú thích, và bộ đệm, và xử lý lỗi thích hợp. Xử lý lỗi? Vâng, hãy đoán xem, việc đưa ra một chuỗi thực sự có thể thất bại và không giống như chương trình C, chương trình C ++ sẽ không bỏ qua điều này một cách im lặng. Xem xét những gìstd::ostreamdưới mui xe, và không có ai nhận ra, nó thực sự khá nhẹ. Không giống như tôi đang sử dụng nó vì tôi ghét cú pháp stream với niềm đam mê. Tuy nhiên, nó vẫn khá tuyệt vời nếu bạn xem xét những gì nó làm.

Nhưng chắc chắn, C ++ nói chung không hiệu quả như C có thể. Nó không thể hiệu quả vì nó không giống nhau và nó không làm điều tương tự. Nếu không có gì khác, C ++ tạo ra các ngoại lệ (và mã để tạo, xử lý hoặc thất bại đối với chúng) và nó mang lại một số đảm bảo mà C không đưa ra. Vì vậy, chắc chắn, một chương trình C ++ nhất thiết phải lớn hơn một chút. Trong bức tranh lớn, tuy nhiên, điều này không quan trọng trong bất kỳ cách nào. Ngược lại, đối với các chương trình thực tế , tôi hiếm khi thấy C ++ hoạt động tốt hơn bởi vì lý do này hay lý do khác, nó dường như cho vay để tối ưu hóa thuận lợi hơn. Đừng hỏi tôi tại sao cụ thể, tôi sẽ không biết.

Nếu, thay vì lửa và quên-hy vọng-tốt nhất bạn quan tâm đến việc viết mã C là chính xác (nghĩa là bạn thực sự kiểm tra lỗi và chương trình hoạt động chính xác khi có lỗi) thì sự khác biệt là không đáng kể, nếu tồn tại.


16
Câu trả lời rất hay, ngoại trừ khẳng định này: Vượt Nhưng chắc chắn, tổng thể C ++ không hiệu quả như C có thể là đơn giản là sai. C ++ có thể được hiệu quả như C, và đủ mã cấp cao có thể là nhiều hơn hiệu quả hơn so với mã C tương đương. Có, C ++ có một số chi phí do phải xử lý các trường hợp ngoại lệ nhưng trên các trình biên dịch hiện đại, chi phí đó không đáng kể so với hiệu suất đạt được từ các bản tóm tắt miễn phí tốt hơn.
Konrad Rudolph

Nếu tôi hiểu chính xác, std::coutném ngoại lệ quá?
Saher

6
@Saher: Vâng, không, có thể. std::coutlà một std::basic_ostreamvà người ta có thể ném, và nó có thể suy nghĩ lại các trường hợp ngoại lệ xảy ra nếu được cấu hình để làm như vậy hoặc nó có thể nuốt các ngoại lệ. Điều quan trọng là, công cụ có thể thất bại, và C ++ cũng như lib tiêu chuẩn C ++ được xây dựng (hầu hết) để các lỗi không dễ dàng được chú ý. Đây là một sự phiền toái một phước lành (nhưng, phước lành nhiều hơn sự khó chịu). Mặt khác, C chỉ cho bạn thấy ngón giữa. Bạn không kiểm tra mã trả lại, bạn không bao giờ biết chuyện gì đã xảy ra.
Damon

1
@KonradRudolph: Đúng, đây là điều tôi đã cố gắng chỉ ra với "Tôi hiếm khi thấy C ++ hoạt động tốt hơn vì lý do này hay lý do khác, nó dường như cho vay để tối ưu hóa thuận lợi hơn. Đừng hỏi tôi tại sao nói riêng" . Không rõ ràng ngay lập tức tại sao, nhưng hiếm khi nó chỉ tối ưu hóa tốt hơn. Vì ... bất cứ lý do gì. Bạn sẽ nghĩ nó hoàn toàn giống với trình tối ưu hóa, nhưng thực tế không phải vậy.
Damon

22

Bạn đang trả tiền cho một sai lầm. Vào những năm 80, khi trình biên dịch không đủ tốt để kiểm tra các chuỗi định dạng, quá tải toán tử được xem là một cách tốt để thực thi một số ngữ nghĩa về an toàn kiểu trong io. Tuy nhiên, mọi tính năng biểu ngữ của nó đều bị triển khai xấu hoặc bị phá sản về mặt khái niệm ngay từ đầu:

<iomanip>

Phần khó chịu nhất của luồng C ++ io api là sự tồn tại của thư viện tiêu đề định dạng này. Bên cạnh việc có trạng thái và xấu và dễ bị lỗi, nó kết hợp định dạng với luồng.

Giả sử bạn muốn in ra một dòng có 8 chữ số không dấu thập lục phân không dấu int theo sau là khoảng trắng theo sau là một chữ số có 3 chữ số thập phân. Với <cstdio>, bạn có thể đọc một chuỗi định dạng súc tích. Với <ostream>, bạn phải lưu trạng thái cũ, đặt căn chỉnh sang phải, đặt ký tự điền, đặt độ rộng điền, đặt cơ sở thành hex, xuất số nguyên, khôi phục trạng thái đã lưu (nếu không định dạng số nguyên của bạn sẽ làm ô nhiễm định dạng float của bạn), xuất không gian , đặt ký hiệu thành cố định, đặt độ chính xác, xuất tín hiệu kép và dòng mới, sau đó khôi phục định dạng cũ.

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Toán tử quá tải

<iostream> là con đẻ của cách không sử dụng quá tải toán tử:

std::cout << 2 << 3 && 0 << 5;

Hiệu suất

std::coutlà nhiều lần chậm hơn printf(). Viêm featur tràn lan và công văn ảo mất đi số điện thoại của nó.

An toàn chủ đề

Cả hai <cstdio><iostream>là chủ đề an toàn trong đó mọi cuộc gọi chức năng là nguyên tử. Nhưng, printf()được thực hiện nhiều hơn mỗi cuộc gọi. Nếu bạn chạy chương trình sau với <cstdio>tùy chọn, bạn sẽ chỉ thấy một hàng f. Nếu bạn sử dụng <iostream>trên một máy đa lõi, bạn có thể sẽ thấy một cái gì đó khác.

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

Việc vặn lại ví dụ này là hầu hết mọi người thực hiện kỷ luật để không bao giờ ghi vào một mô tả tệp duy nhất từ ​​nhiều luồng. Chà, trong trường hợp đó, bạn sẽ phải quan sát rằng <iostream>sẽ giúp lấy một cái khóa một cách hữu ích trên mọi <<thứ >>. Trong khi đó <cstdio>, bạn sẽ không bị khóa thường xuyên và thậm chí bạn có tùy chọn không khóa.

<iostream> sử dụng nhiều khóa hơn để đạt được kết quả ít nhất quán hơn.


2
Hầu hết các triển khai của printf có một tính năng cực kỳ hữu ích để bản địa hóa: các tham số được đánh số. Nếu bạn cần tạo ra một số đầu ra bằng hai ngôn ngữ khác nhau (như tiếng Anh và tiếng Pháp) và thứ tự từ khác nhau, bạn có thể sử dụng cùng một printf với một chuỗi định dạng khác nhau và nó có thể in các tham số theo thứ tự khác nhau.
gnasher729

2
Định dạng trạng thái của các luồng phải đưa ra rất nhiều lỗi khó tìm Tôi không biết phải nói gì. Câu trả lời chính xác. Sẽ upvote nhiều hơn một lần nếu tôi có thể.
mathreadler

6
std::coutLà chậm hơn nhiều lần printf()” - Tuyên bố này được lặp đi lặp lại khắp nơi trên net nhưng nó đã không đúng trong lứa tuổi. Các triển khai IOux hiện đại thực hiện ngang bằng với printf. Cái sau cũng thực hiện công văn ảo trong nội bộ để xử lý các luồng được đệm và IO cục bộ (được thực hiện bởi hệ điều hành nhưng vẫn được thực hiện).
Konrad Rudolph

3
@KevinZ Và điều đó thật tuyệt nhưng đó là điểm chuẩn cho một cuộc gọi cụ thể, duy nhất thể hiện các thế mạnh cụ thể của fmt (rất nhiều định dạng khác nhau trong một chuỗi). Trong sử dụng điển hình hơn sự khác biệt giữa printfcoutthu nhỏ. Ngẫu nhiên có hàng tấn điểm chuẩn như vậy trên chính trang web này.
Konrad Rudolph

3
@KonradRudolph Điều đó cũng không đúng. Microbenchmark thường đánh giá thấp chi phí phình to và thiếu quyết đoán vì chúng không làm cạn kiệt một số tài nguyên hạn chế (có thể là thanh ghi, icache, bộ nhớ, bộ dự báo nhánh) trong đó một chương trình thực sẽ. Khi bạn ám chỉ đến "cách sử dụng điển hình hơn", về cơ bản, nó nói rằng bạn có sự phình to hơn đáng kể ở nơi khác, điều đó tốt, nhưng lạc đề. Theo tôi, nếu bạn không có yêu cầu về hiệu suất, bạn không cần phải lập trình trong C ++.
KevinZ

18

Ngoài những gì tất cả các câu trả lời khác đã nói,
đó cũng là thực tế rằng std::endlkhông giống như '\n'.

Đây là một quan niệm sai lầm đáng tiếc phổ biến. std::endlkhông có nghĩa là "dòng mới",
nó có nghĩa là "in dòng mới và sau đó tuôn ra luồng ". Xả nước không rẻ!

Hoàn toàn bỏ qua sự khác biệt giữa printfstd::couttrong một thời điểm, để có hiệu quả về mặt chức năng đối với ví dụ C của bạn, ví dụ C ++ của bạn phải giống như thế này:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

Và đây là một ví dụ về những ví dụ của bạn sẽ như thế nào nếu bạn bao gồm việc xả nước.

C

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C ++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

Khi so sánh mã, bạn phải luôn cẩn thận rằng bạn đang so sánh lượt thích và bạn hiểu ý nghĩa của việc mã của bạn đang làm. Đôi khi ngay cả những ví dụ đơn giản nhất cũng phức tạp hơn một số người nhận ra.


Trên thực tế, sử dụng std::endl chức năng tương đương với việc viết một dòng mới vào luồng stdio được đệm dòng. stdout, đặc biệt, được yêu cầu phải được đệm hoặc không có bộ đệm khi được kết nối với một thiết bị tương tác. Linux, tôi tin rằng, nhấn mạnh vào tùy chọn đệm dòng.

Trên thực tế, thư viện iostream không có chế độ đệm dòng ... cách để đạt được hiệu quả của bộ đệm dòng chính xác là sử dụng std::endlđể xuất ra dòng mới.

@Hurkyl khăng khăng? Vậy thì công dụng của nó là setvbuf(3)gì? Hoặc bạn có nghĩa là để mặc định là dòng đệm? FYI: Thông thường tất cả các tệp được đệm. Nếu một luồng đề cập đến một thiết bị đầu cuối (như thiết bị xuất chuẩn thông thường), thì đó là dòng được đệm. Mặc định luồng lỗi tiêu chuẩn luôn luôn không được xử lý theo mặc định.
Pryftan

Không printftự động tuôn ra khi gặp một nhân vật dòng mới?
bool3max

1
@ bool3max Điều đó chỉ cho tôi biết môi trường của tôi làm gì, nó có thể khác ở những môi trường khác. Ngay cả khi nó hoạt động giống nhau trong tất cả các triển khai phổ biến nhất, điều đó không có nghĩa là có một trường hợp cạnh ở đâu đó. Đó là lý do tại sao tiêu chuẩn rất quan trọng - tiêu chuẩn cho biết liệu một cái gì đó phải giống nhau cho tất cả các triển khai hay liệu nó có được phép thay đổi giữa các lần thực hiện hay không.
Pharap

16

Mặc dù các câu trả lời kỹ thuật hiện có là chính xác, tôi nghĩ rằng câu hỏi cuối cùng bắt nguồn từ quan niệm sai lầm này:

Điều nổi tiếng là trong C ++, bạn phải trả cho những gì bạn ăn.

Đây chỉ là cuộc nói chuyện tiếp thị từ cộng đồng C ++. (Công bằng mà nói, có cuộc nói chuyện tiếp thị trong mọi cộng đồng ngôn ngữ.) Điều đó không có nghĩa là bất cứ điều gì cụ thể mà bạn có thể phụ thuộc nghiêm túc.

"Bạn trả tiền cho những gì bạn sử dụng" được cho là có nghĩa là tính năng C ++ chỉ có chi phí hoạt động nếu bạn đang sử dụng tính năng đó. Nhưng định nghĩa của "một tính năng" không phải là vô cùng chi tiết. Thường thì bạn sẽ kết thúc kích hoạt các tính năng có nhiều khía cạnh và mặc dù bạn chỉ cần một tập hợp con của các khía cạnh đó, nhưng việc triển khai để đưa tính năng này một phần là không thực tế hoặc có thể.

Nói chung, nhiều ngôn ngữ (mặc dù không phải là tất cả) cố gắng để có hiệu quả, với mức độ thành công khác nhau. C ++ nằm ở đâu đó trên bàn cân, nhưng không có gì đặc biệt hay kỳ diệu về thiết kế của nó sẽ cho phép nó thành công hoàn hảo trong mục tiêu này.


1
Chỉ có hai điều tôi có thể nghĩ về nơi bạn trả tiền cho những thứ bạn không sử dụng: ngoại lệ và RTTI. Và tôi không nghĩ đó là cuộc nói chuyện tiếp thị; C ++ về cơ bản là một C mạnh hơn, cũng là "không trả tiền cho những gì bạn sử dụng".
Rakete1111

2
@ Rakete1111 Từ lâu, nếu ngoại lệ không ném, họ sẽ không mất phí. Nếu chương trình của bạn được ném liên tục, nó sẽ được thiết kế lại. Nếu tình trạng lỗi nằm ngoài tầm kiểm soát của bạn, bạn nên kiểm tra tình trạng bằng kiểm tra độ tỉnh táo của bool, trước khi gọi phương thức dựa trên điều kiện là sai.
schulmaster

1
@schulmaster: Các ngoại lệ có thể áp đặt các ràng buộc thiết kế khi mã được viết bằng C ++ cần tương tác với mã được viết bằng các ngôn ngữ khác, vì việc chuyển điều khiển không cục bộ chỉ có thể hoạt động trơn tru giữa các mô-đun nếu các mô-đun biết cách phối hợp với nhau.
supercat

1
(mặc dù có thể nói là không phải tất cả) các ngôn ngữ phấn đấu để có hiệu quả . Chắc chắn không phải tất cả: Ngôn ngữ lập trình bí truyền cố gắng trở nên mới lạ / thú vị, không hiệu quả. esolangs.org . Một số trong số họ, như BrainFuck, nổi tiếng là không hiệu quả. Hoặc ví dụ, Ngôn ngữ lập trình Shakespeare, kích thước tối thiểu 227 byte (codegolf) để In tất cả các số nguyên . Trong số các ngôn ngữ dành cho sử dụng sản xuất, hầu hết đều nhắm đến hiệu quả, nhưng một số ngôn ngữ (như bash) chủ yếu nhằm mục đích thuận tiện và được biết là chậm.
Peter Cordes

2
Vâng, đó là tiếp thị nhưng nó gần như hoàn toàn đúng. Bạn có thể dính vào <cstdio>và không bao gồm <iostream>, giống như cách bạn có thể biên dịch -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables.
KevinZ

11

Các chức năng Nhập / Xuất trong C ++ được viết thanh lịch và được thiết kế sao cho đơn giản để sử dụng. Trong nhiều khía cạnh, chúng là một chương trình giới thiệu cho các tính năng hướng đối tượng trong C ++.

Nhưng bạn thực sự từ bỏ một chút hiệu suất để trả lại, nhưng điều đó không đáng kể so với thời gian mà hệ điều hành của bạn sử dụng để xử lý các chức năng ở mức thấp hơn.

Bạn luôn có thể quay trở lại các chức năng kiểu C vì chúng là một phần của tiêu chuẩn C ++ hoặc có thể từ bỏ tính di động hoàn toàn và sử dụng các cuộc gọi trực tiếp đến hệ điều hành của bạn.


23
"Các chức năng Đầu vào / Đầu ra trong C ++ là những con quái vật gớm ghiếc đang cố gắng che giấu bản chất Cthulian của chúng đằng sau một veneer mỏng hữu dụng. Trong nhiều khía cạnh, chúng là một ví dụ về cách không thiết kế mã C ++ hiện đại". Có lẽ sẽ chính xác hơn.
dùng673679

3
@ user673679: Rất đúng. Vấn đề lớn với các luồng I / O của C ++ là những gì bên dưới: thực sự có rất nhiều điều phức tạp đang diễn ra và bất cứ ai từng xử lý chúng (tôi đang đề cập std::basic_*streamxuống) đều biết những cơn đau đầu đến. Chúng được thiết kế để được phổ biến rộng rãi và mở rộng thông qua thừa kế; nhưng cuối cùng không ai làm điều đó, vì sự phức tạp của chúng (có nghĩa là những cuốn sách được viết trên iostreams), đến nỗi các thư viện mới được sinh ra chỉ vì điều đó (ví dụ: boost, ICU, v.v.). Tôi nghi ngờ chúng ta sẽ ngừng trả tiền cho sai lầm này.
edmz

1

Như bạn đã thấy trong các câu trả lời khác, bạn trả tiền khi bạn liên kết trong các thư viện chung và gọi các nhà xây dựng phức tạp. Không có câu hỏi cụ thể ở đây, nhiều hơn một nắm. Tôi sẽ chỉ ra một số khía cạnh trong thế giới thực:

  1. Barne có một nguyên tắc thiết kế cốt lõi để không bao giờ để hiệu quả là lý do để ở lại C hơn là C ++. Điều đó nói rằng, người ta cần phải cẩn thận để có được những hiệu quả này, và có những hiệu quả không thường xuyên luôn hoạt động nhưng không "về mặt kỹ thuật" trong thông số kỹ thuật C. Ví dụ, cách bố trí các trường bit không thực sự được chỉ định.

  2. Hãy thử nhìn qua Ostream. Ôi trời ơi! Tôi sẽ không ngạc nhiên khi tìm thấy một chuyến bay giả lập ở đó. Ngay cả printf () của stdlib cũng thường chạy khoảng 50K. Đây không phải là những lập trình viên lười biếng: một nửa kích thước printf là do các đối số chính xác gián tiếp mà hầu hết mọi người không bao giờ sử dụng. Hầu như mọi thư viện của bộ xử lý thực sự bị ràng buộc đều tạo mã đầu ra riêng thay vì printf.

  3. Sự gia tăng kích thước thường mang lại trải nghiệm linh hoạt và chứa đựng hơn. Tương tự như vậy, một máy bán hàng tự động sẽ bán một tách chất giống như cà phê cho một vài đồng xu và toàn bộ giao dịch diễn ra trong vòng một phút. Rơi vào một nhà hàng tốt bao gồm một thiết lập bàn, được ngồi, đặt hàng, chờ đợi, nhận được một chiếc cốc đẹp, nhận được hóa đơn, thanh toán theo hình thức của bạn, thêm tiền boa, và chúc một ngày tốt lành trên đường ra ngoài. Đó là một trải nghiệm khác biệt, và thuận tiện hơn nếu bạn ghé qua với bạn bè cho một bữa ăn phức tạp.

  4. Mọi người vẫn viết ANSI C, mặc dù hiếm khi K & R C. Kinh nghiệm của tôi là chúng tôi luôn biên dịch nó với trình biên dịch C ++ bằng cách sử dụng một vài điều chỉnh cấu hình để hạn chế những gì được kéo vào. ; đã có một số đối số tốt để đóng gói trường thông minh hơn và bố trí bộ nhớ. IMHO Tôi nghĩ rằng bất kỳ thiết kế ngôn ngữ nào cũng nên bắt đầu bằng một danh sách các mục tiêu, giống như Zen của Python .

Đó là một cuộc thảo luận thú vị. Bạn hỏi tại sao bạn không thể có những thư viện nhỏ, đơn giản, thanh lịch, đầy đủ và linh hoạt?

Không có câu trả lời. Sẽ không có câu trả lời. Đó là câu trả lời.

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.