it-swarm.com.ru

В C ++ я плачу за то, что не ем?

Давайте рассмотрим следующие примеры hello world на C и 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;
}

Когда я компилирую их в Godbolt для Assembly, размер кода C составляет всего 9 строк (gcc -O3):

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

Но размер кода C++ составляет 22 строки (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

... что намного больше.

Известно, что в C++ вы платите за то, что едите. Итак, в таком случае, за что я плачу?

165
Saher

Вы платите за то, чтобы вызывать тяжелую библиотеку (не такую ​​тяжелую, как печать в консоли). Вы инициализируете объект ostream. Есть несколько скрытых хранилищ. Затем вы вызываете std::endl, который не является синонимом \n. Библиотека iostream помогает вам настроить множество параметров и обременять процессор, а не программиста. Это то, за что вы платите.

Давайте рассмотрим код:

.LC0:
        .string "Hello world"
main:

Инициализация объекта 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)

Повторный вызов cout для печати новой строки и сброса

    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

Также важно различать язык и библиотеку.

Кстати, это только часть истории. Вы не знаете, что написано в функциях, которые вы вызываете.

58
Arash

Итак, в таком случае, за что я плачу?

std::cout более мощный и сложный, чем printf. Он поддерживает такие вещи, как локали, флаги форматирования с учетом состояния и многое другое.

Если они вам не нужны, используйте std::printf или std::puts - они доступны в <cstdio>.


Известно, что в C++ вы платите за то, что едите.

Я также хочу прояснить, что C++ ! = Стандартная библиотека C++. Стандартная библиотека должна быть универсальной и "достаточно быстрой", но часто она будет медленнее, чем специализированная реализация того, что вам нужно.

С другой стороны, язык C++ стремится сделать возможным написание кода без дополнительных ненужных скрытых затрат (например, opt-in virtual, без сборки мусора).

209
Vittorio Romeo

Вы не сравниваете C и C++. Вы сравниваете printf и std::cout, которые способны на разные вещи (локали, форматирование с сохранением состояния и т.д.).

Попробуйте использовать следующий код для сравнения. Godbolt генерирует одинаковую сборку для обоих файлов (протестировано с gcc 8.2, -O3).

main.c:

#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;
}

main.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);
    }
}
173
pschill

Ваши списки действительно сравнивают яблоки и апельсины, но не по той причине, которая указана в большинстве других ответов.

Давайте проверим, что на самом деле делает ваш код:

C:

  • вывести одну строку, "Hello world\n"

C++:

  • передать строку "Hello world" в std::cout
  • поток std::endl манипулятор в std::cout

По-видимому, ваш код C++ выполняет в два раза больше работы. Для честного сравнения мы должны объединить это:

#include <iostream>

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

… И вдруг ваш ассемблерный код для main очень похож на 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

Фактически, мы можем сравнивать код C и C++ построчно, и есть очень мало различий:

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

Единственная реальная разница в том, что в C++ мы вызываем operator << с двумя аргументами (std::cout и строка). Мы могли бы устранить даже эту небольшую разницу, используя более близкий C-эквивалент: fprintf, у которого также есть первый аргумент, указывающий поток.

Это оставляет код ассемблера для _GLOBAL__sub_I_main, который генерируется для C++, но не для C. Это единственные истинные издержки, которые видны в этом списке ассемблера (конечно, есть больше невидимых накладных расходов для обоих языков). Этот код выполняет однократную настройку некоторых функций стандартной библиотеки C++ при запуске программы C++.

Но, как объяснено в других ответах, соответствующая разница между этими двумя программами не будет обнаружена в выводе Assembly функции main, поскольку вся тяжелая работа происходит за кулисами.

133
Konrad Rudolph

Известно, что в C++ вы платите за то, что едите. Итак, в таком случае, за что я плачу?

Это просто Вы платите за std::cout. "Вы платите только за то, что едите", не означает "вы всегда получаете лучшие цены". Конечно, printf дешевле. Можно утверждать, что std::cout более безопасен и более универсален, поэтому его большая стоимость оправдана (стоит дороже, но обеспечивает большую ценность), но это упускает из виду. Вы не используете printf, вы используете std::cout, поэтому вы платите за использование std::cout. Вы не платите за использование printf.

Хороший пример - виртуальные функции. Виртуальные функции имеют некоторые затраты времени выполнения и требования к пространству - но только если вы фактически используете их. Если вы не используете виртуальные функции, вы ничего не платите.

Несколько замечаний

  1. Даже если код C++ оценивается в большее количество инструкций на ассемблере, он все равно является небольшим количеством инструкций, и любые накладные расходы по производительности все еще, вероятно, уменьшаются фактическими операциями ввода-вывода.

  2. На самом деле, иногда это даже лучше, чем "в C++ вы платите за то, что едите". Например, компилятор может сделать вывод, что вызов виртуальной функции не требуется в некоторых обстоятельствах, и преобразовать его в не виртуальный вызов. Это означает, что вы можете получить виртуальные функции для бесплатно. Разве это не здорово?

53
el.pescado

"Список сборок для printf" НЕ для printf, а для put (тип оптимизации компилятора?); printf гораздо сложнее, чем puts ... не забывайте!

47
Álvaro Gustavo López

Я вижу некоторые правильные ответы здесь, но я собираюсь немного подробнее рассказать о них.

Перейдите к приведенной ниже сводке, чтобы найти ответ на свой главный вопрос, если вы не хотите проходить через всю эту стену текста.


Абстракция

Итак, в таком случае, за что я плачу?

Вы платите за абстракцию . Возможность писать более простой и понятный для человека код обходится дорого. В C++, который является объектно-ориентированным языком, почти все является объектом. Когда вы используете какой-либо предмет, под капотом всегда будут происходить три главных вещи:

  1. Создание объекта, в основном выделение памяти для самого объекта и его данных.
  2. Инициализация объекта (обычно с помощью некоторого метода init()). Обычно выделение памяти происходит под капотом, как первое на этом этапе.
  3. Разрушение объекта (не всегда).

Вы не видите этого в коде, но каждый раз, когда вы используете объект, все три вышеперечисленных вещи должны как-то происходить. Если бы вы делали все вручную, код, очевидно, был бы намного длиннее.

Теперь абстракция может быть сделана эффективно, без добавления накладных расходов: компиляция методов и другие методы могут использоваться как компиляторами, так и программистами для устранения издержек абстракции, но это не ваш случай.

Что на самом деле происходит в C++?

Вот оно, сломано:

  1. Класс std::ios_base инициализирован, который является базовым классом для всего, что связано с вводом/выводом.
  2. Объект std::cout инициализирован.
  3. Ваша строка загружается и передается в std::__ostream_insert, который (как вы уже поняли по имени) является методом std::cout (в основном оператором <<), который добавляет строку в поток.
  4. cout::endl также передается std::__ostream_insert.
  5. __std_dso_handle передается __cxa_atexit, который является глобальной функцией, которая отвечает за "очистку" перед выходом из программы. Сама функция __std_dso_handle вызывается для освобождения и уничтожения оставшихся глобальных объектов.

Так что использование C == ничего не платит?

В коде C происходит очень мало шагов:

  1. Ваша строка загружается и передается puts через регистр edi.
  2. puts вызывается.

Никаких объектов нигде, следовательно, не нужно ничего инициализировать/уничтожать.

Это, однако, не означает, что вы ничего не "платите" в C . Вы все еще платите за абстракцию, а также инициализация стандартной библиотеки C и динамического разрешения. Функция printf (или, собственно, puts, которая оптимизирована компилятором, так как вам не нужна строка формата) все еще происходит под капотом.

Если бы вы написали эту программу на чистом ассемблере, она бы выглядела примерно так:

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

Что в основном приводит только к вызову writesyscall , за которым следует exit syscall. Теперь это было бы минимальным, чтобы выполнить то же самое.


Подвести итоги

C является гораздо более голым и выполняет только необходимый минимум, оставляя полный контроль над пользователем, который способен полностью оптимизировать и настроить в основном все, что они хотят. Вы говорите процессору загрузить строку в регистр, а затем вызываете библиотечную функцию для использования этой строки. C++, с другой стороны, более сложный и абстрактный . Это имеет огромное преимущество при написании сложного кода и позволяет легче писать и более дружественный к человеку код, но это, очевидно, обходится дорого. В C++ всегда будет недостаток в производительности по сравнению с C в подобных случаях, поскольку C++ предлагает больше, чем необходимо для выполнения таких базовых задач, и, следовательно, добавляет больше накладных расходов .

Отвечая на ваш главный вопрос :

Я плачу за то, что не ем?

В этом конкретном случае да . Вы не пользуетесь преимуществами того, что C++ может предложить больше, чем C, но это потому, что в этом простом куске кода нет ничего, что могло бы вам помочь C++: это настолько просто, что вам действительно не нужен C++ вообще.


Ох, и еще одна вещь!

На первый взгляд преимущества 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;
}

Надеюсь, вы можете ясно увидеть, что я имею в виду здесь. Также обратите внимание на то, как в C вы должны управлять памятью на более низком уровне, используя malloc и free, как вам нужно быть более осторожным с индексированием и размерами, и как вы должны быть очень конкретны при вводе и печати.

44
Marco Bonelli

Есть несколько заблуждений, чтобы начать с. Во-первых, программа C++ не приводит к 22 инструкциям, это больше похоже на 22 000 (я вытащил это число из своей шляпы, но оно примерно в поле). Кроме того, код C не приводит также к 9 инструкциям. Это только те, которые вы видите.

Что делает код C, так это то, что после выполнения многих вещей, которые вы не видите, он вызывает функцию из CRT (которая обычно, но не обязательно присутствует в качестве разделяемой библиотеки), затем не проверьте возвращаемое значение или обработайте ошибки, и выручите. В зависимости от компилятора и настроек оптимизации он даже не вызывает printf, а puts, или что-то еще более примитивное.
Вы могли бы написать более или менее одну и ту же программу (за исключением некоторых невидимых функций инициализации) и на C++, если бы вы вызывали эту же функцию одинаково. Или, если вы хотите быть супер-корректным, та же самая функция с префиксом std::.

Соответствующий код C++ на самом деле совсем не одно и то же. Хотя весь <iostream> хорошо известен тем, что он толстый уродливый поросенок, который добавляет огромные накладные расходы для небольших программ (в "настоящей" программе вы на самом деле не замечаете так много), несколько более справедливая интерпретация заключается в том, что она делает очень много вещей, которые вы не видите и которые просто работает. Включая, но не ограничиваясь этим, магическое форматирование практически любого случайного материала, включая различные числовые форматы и локали и тому подобное, буферизацию и правильную обработку ошибок. Обработка ошибок? Ну да, угадайте, что, вывод строки может действительно потерпеть неудачу, и в отличие от программы на C, программа на C++ не игнорирует это молча. Учитывая то, что std::ostream делает под капотом, и никто об этом не узнает, на самом деле он довольно легкий. Не то, чтобы я использовал это, потому что я ненавижу потоковый синтаксис со страстью. Но все же, это довольно круто, если учесть, что он делает.

Но, конечно, C++ в целом не настолько эффективен, насколько может быть C. Это не может быть так эффективно, так как это не одно и то же, и это не делает одно и то же. Если ничего другого, C++ генерирует исключения (и код для их генерации, обработки или сбоя) и дает некоторые гарантии, которые C не дает. Так что, конечно, программа на C++ должна быть немного больше. В целом, однако, это не имеет никакого значения. Напротив, для реальных программ я не редко обнаруживал, что C++ работает лучше, потому что по тем или иным причинам он, по-видимому, дает более благоприятные оптимизации. Не спрашивайте меня, почему, в частности, я бы не знал.

Если вместо того, чтобы бросить огонь и забыть надежду на лучшее, вы хотите написать код на языке C, который правильный (т.е. вы фактически проверяете на наличие ошибок, и программа ведет себя правильно при наличии ошибок ) тогда разница незначительна, если существует.

27
Damon

Вы платите за ошибку. В 80-х, когда компиляторы не были достаточно хороши для проверки строк форматирования, перегрузка операторов рассматривалась как хороший способ обеспечить некоторое подобие безопасности типов во время ввода-вывода. Тем не менее, каждая из его функций баннера с самого начала либо реализована плохо, либо концептуально банкротом:

<Iomanip>

Наиболее отвратительной частью потока C++ io api является наличие этой библиотеки заголовков форматирования. Помимо сохранения состояния, уродства и ошибок, он объединяет форматирование с потоком.

Предположим, что вы хотите вывести строку с шестнадцатеричным заполненным нулями int без знака int, за которым следует пробел, за которым следует двойное число с 3 десятичными знаками. С <cstdio> вы можете прочитать краткую строку формата. С <ostream>, вы должны сохранить старое состояние, установить выравнивание по правому краю, установить символ заливки, установить ширину заливки, установить основание на шестнадцатеричное, вывести целое число, восстановить сохраненное состояние (в противном случае ваше целочисленное форматирование будет загрязнять форматирование с плавающей запятой), вывести пробел, установите фиксированную запись, установите точность, выведите double и новую строку, затем восстановите старое форматирование.

// <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);

Перегрузка оператора

<iostream> - это потомок того, как не использовать перегрузку операторов:

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

Спектакль

std::cout в несколько раз медленнее printf(). Безудержный фурит и виртуальная отправка делают свое дело.

Поток безопасности

И <cstdio>, и <iostream> потокобезопасны в том смысле, что каждый вызов функции является атомарным. Но printf() делает намного больше за один вызов. Если вы запустите следующую программу с параметром <cstdio>, вы увидите только строку f. Если вы используете <iostream> на многоядерном компьютере, вы, скорее всего, увидите что-то еще.

// 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
}

Ответом на этот пример является то, что большинство людей применяют дисциплину, чтобы в любом случае никогда не писать в один файловый дескриптор из нескольких потоков. Что ж, в этом случае вы должны будете заметить, что <iostream> будет полезно захватывать блокировку каждого << и каждого >>. В то время как в <cstdio> вы не будете блокировать так часто, и у вас даже есть возможность не блокировать.

<iostream> увеличивает количество блокировок для достижения менее согласованного результата.

22
KevinZ

В дополнение к тому, что сказали все остальные ответы,
также существует тот факт, что std::endlне то же самое, что '\n'.

Это, к сожалению, распространенное заблуждение. std::endl не означает "новая строка",
это означает "напечатать новую строку а затем очистить поток". Промывка не дешевая!

На мгновение полностью игнорируем различия между printf и std::cout, чтобы функционально соответствовать вашему примеру C, ваш пример C++ должен выглядеть следующим образом:

#include <iostream>

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

И вот пример того, какими должны быть ваши примеры, если вы включите сброс.

C

#include <stdio.h>

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

С ++

#include <iostream>

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

При сравнении кода вы всегда должны быть осторожны, сравнивая подобное с like и понимая последствия того, что делает ваш код. Иногда даже самые простые примеры сложнее, чем думают некоторые люди.

18
Pharap

Хотя существующие технические ответы верны, я думаю, что вопрос в конечном счете вытекает из этого заблуждения:

Известно, что в C++ вы платите за то, что едите.

Это всего лишь маркетинговый разговор сообщества C++. (Если честно, в каждом языковом сообществе говорят о маркетинге.) Это не означает ничего конкретного, от чего вы можете серьезно зависеть.

"Вы платите за то, что используете", как предполагается, означает, что функция C++ имеет накладные расходы, только если вы используете эту функцию. Но определение "функции" не является бесконечно детализированным. Часто вы заканчиваете тем, что активируете функции, которые имеют несколько аспектов, и даже если вам требуется только подмножество этих аспектов, это часто не практично или невозможно для реализации, чтобы привести функцию в частично.

В целом, многие (хотя, возможно, не все) языки стремятся быть эффективными с разной степенью успеха. C++ где-то в масштабе, но нет ничего особенного или волшебного в его дизайне, который позволил бы ему быть совершенно успешным в этой цели.

16
Theodoros Chatzigiannakis

Функции ввода/вывода в C++ написаны элегантно и разработаны так, чтобы они были просты в использовании. Во многих отношениях они являются витриной для объектно-ориентированных функций в C++.

Но вы действительно отдаете немного производительности взамен, но это ничтожно мало по сравнению со временем, затрачиваемым вашей операционной системой на обработку функций на более низком уровне.

Вы всегда можете вернуться к функциям стиля C, поскольку они являются частью стандарта C++, или, возможно, вообще отказаться от переносимости и использовать прямые вызовы вашей операционной системы.

12
Bathsheba

Как вы видели в других ответах, вы платите, когда ссылаетесь в общие библиотеки и вызываете сложные конструкторы. Здесь нет никакого особого вопроса, больше жалобы. Я укажу некоторые реальные аспекты:

  1. У Барна был основной принцип проектирования, который никогда не позволял эффективности быть причиной оставаться в C, а не в C++. Тем не менее, нужно быть осторожным, чтобы получить эти эффективности, и есть случайные эффективности, которые всегда работали, но не были "технически" в спецификации C. Например, расположение битовых полей на самом деле не было указано.

  2. Попробуйте посмотреть через ostream. Боже мой, это раздутый! Я не удивлюсь, если найду симулятор полета там. Даже printf () в stdlib обычно работает около 50K. Это не ленивые программисты: половина размера printf была связана с косвенными аргументами точности, которые большинство людей никогда не используют. Почти каждая действительно ограниченная библиотека процессора создает свой собственный выходной код вместо printf.

  3. Увеличение размера обычно обеспечивает более ограниченный и гибкий опыт. По аналогии, торговый автомат продаст чашку кофеоподобного вещества за несколько монет, и вся транзакция займет меньше минуты. Заход в хороший ресторан включает в себя сервировку стола, сидение, заказ, ожидание, получение чашки Nice, получение счета, оплату выбранных вами форм, добавление чаевых и пожелание хорошего дня на выходе. Это другой опыт, и более удобный, если вы заходите с друзьями на сложную трапезу.

  4. Люди по-прежнему пишут ANSI C, хотя редко K & R C. Мой опыт показывает, что мы всегда компилируем его с помощью компилятора C++, используя несколько настроек, чтобы ограничить то, что перетаскивается. Для других языков есть хорошие аргументы: Go удаляет полиморфные издержки и сумасшедший препроцессор ; было несколько хороших аргументов в пользу более разумной упаковки полей и размещения памяти. ИМХО, я думаю, что любой языковой дизайн должен начинаться с перечисления целей, очень как Zen of Python .

Это была веселая дискуссия. Вы спрашиваете, почему у вас не может быть волшебно маленьких, простых, элегантных, полных и гибких библиотек?

Там нет ответа. Там не будет ответа. Это ответ.

1
Charles Merriam