Каковы исторические причины, по которым в языках C есть преинкременты и постинкременты?

(Примечание: я не спрашиваю об определениях пре-инкремента и пост-инкремента или о том, как они используются в C/C++. Поэтому я не думаю, что это повторяющийся вопрос.)

Разработчики C (Деннис Ритчи и др.) создали операторы инкремента и декремента по очень веским причинам. Чего я не понимаю, так это почему они решили провести различие между пре- и пост-инкрементами/декрементами?

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

Почему они решили это сделать, и что изменилось в вычислениях, что это различие сегодня не так полезно?

Для справки, разницу между ними можно увидеть в коде C++:

int x = 3;

cout << "x = 3; x++ == " << x++ << endl;
cout << "++x == " << ++x << endl;
cout << "x-- == " << x-- << endl;
cout << "--x == " << --x << endl;

даст в качестве вывода

x++ == 3
++x == 5
x-- == 5
--x == 3

person ShanZhengYang    schedule 25.05.2015    source источник
comment
Развитие языка C (Ритчи) содержит абзац о операторы приращения, а также постфикс/префикс, но не вдаваться в подробности.   -  person dyp    schedule 25.05.2015
comment
Побочный эффект - это то, что полезно.   -  person Fiddling Bits    schedule 25.05.2015
comment
[Предупреждение о предположениях] При системном программировании на C или C++ вы в конечном итоге пишете много кода. Все, что помогает вам оставаться кратким, хорошо. Операторы префикса и постфикса позволяют программистам тратить меньше места на жонглирование переменными. Я думаю, поэтому они добавили его. Однако я удивлен, что в C нет оператора замены переменной-значения.   -  person Dai    schedule 25.05.2015
comment
@Dai, когда я начал изучать C, я использовал бы любой трюк, который выглядел бы l33t, сегодня я уважаю POLA и различные другие принципы. Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ~Martin Fowler   -  person v.oddou    schedule 25.05.2015
comment
@v.oddou есть разница между лаконичностью и неясностью или загадочностью.   -  person Dai    schedule 25.05.2015
comment
Что заставляет вас утверждать, что изменилось в вычислениях, что это различие сегодня не так полезно? Для процессоров, в которых отсутствуют отдельные инструкции inc/dec, их можно тривиально заменить на add r0,r0,#1 (пример ARM). Различие между post и pre сегодня важно как никогда.   -  person Jongware    schedule 25.05.2015
comment
Моя теория связана с управлением стеком. Чтобы поместить элемент в стек, вам нужно увеличить его после, а чтобы извлечь этот элемент, вам нужно уменьшить его до.   -  person Galik    schedule 25.05.2015
comment
@ShanZhengYang, есть ли у вас какие-либо статистические данные, скажем, подкрепленные анализом группы проектов с открытым исходным кодом или программистов, подтверждающие предположения в этом вопросе? И вызов переменной three вместо x, когда вы планируете мутировать, кажется преднамеренной попыткой изобразить эти операторы как запутанные.   -  person Tony Delroy    schedule 25.05.2015
comment
@TonyD Вы можете редактировать код. Это довольно ясно в моей книге, но вы читатель. Кроме того, это интернет-дискуссия, а не публикация. Мне нет нужды проводить многомиллионный опрос, чтобы показать, что программисты думают о до- и после-. Я основываюсь исключительно на личном опыте, личных беседах и на том факте, что на StackExchange есть десятки постов, посвященных этому. Кроме того, было бы вредно, если бы я публиковал вопросы, если бы у меня была существенная статистика, которую вы рекомендуете. Это не то, как STEM-специалисты обсуждают проблемы между собой.   -  person ShanZhengYang    schedule 25.05.2015
comment
@ShanZhengYang: код отредактирован; ценю ваше примечание о неофициальных свидетельствах. Ваше здоровье.   -  person Tony Delroy    schedule 26.05.2015
comment
Связано: programmers.stackexchange.com/q/331870/33478   -  person Keith Thompson    schedule 26.09.2016


Ответы (6)


В то время увеличение и уменьшение на 1 широко поддерживалось оборудованием: один код операции и быстро. Это потому, что «увеличение на 1» и «уменьшение на 1» были очень распространенными операциями в коде (верно и по сей день).

Пост- и преддекрементные формы повлияли только на то место, где этот код операции был вставлен в сгенерированный машинный код. Концептуально это имитирует «увеличение/уменьшение до или после использования результата». В одном заявлении

i++;

концепция «до/после» не используется (поэтому она делает то же самое, что и ++i;), но в

printf ("%d", ++i);

Это. Это различие так же важно в наши дни, как и при разработке языка C (эта конкретная идиома была скопирована с его предшественника под названием «B»).

Из Разработка языка C

Эта функция ["автоинкрементные" ячейки памяти PDP-7], вероятно, подсказала таких операторов Томпсону [Кен Томпсон, который разработал "B", предшественника C]; обобщение сделать их и префиксом, и постфиксом было его собственным. Действительно, ячейки автоинкремента не использовались непосредственно в реализации операторов, и более сильной мотивацией для нововведения, вероятно, было его наблюдение, что перевод ++x был меньше, чем перевод x=x+1.

Спасибо @dyp за упоминание этого документа.

person Jongware    schedule 25.05.2015
comment
Ritchie, The Development of the C Language: Люди часто предполагают, что они были созданы для использования режимов автоинкремента и автодекремента адресов, предоставляемых DEC PDP-11, на котором C и Unix впервые стали популярными. Это исторически невозможно, так как не было PDP-11, когда разрабатывался B. PDP-7, похоже, имеет некоторые особенности, которые могли сыграть свою роль, хотя я не думаю, что из документа полностью ясно, что они были основной причиной существования как префикса, так и постфикса ++. - person dyp; 25.05.2015
comment
@dyp: как бы то ни было, я не верю, что они изобрели это из ничего. Инструкция inc X могла существовать до появления PDP-11. - person Jongware; 25.05.2015
comment
Я пересмотрел свой комментарий. Производительность могла быть одним из аспектов, но я не думаю, что коды операций могут показать всю картину. - person dyp; 25.05.2015
comment
@dyp: прекрасное чтение. Я включил соответствующую цитату, поскольку она кажется совершенно уместной. - person Jongware; 25.05.2015
comment
the translation was smaller. вот вам и истерический изюм: отсутствие оптимизатора в ранних компиляторах. - person v.oddou; 25.05.2015
comment
B активно работал на Honeywell 6070, который имел < инструкция href="http://www.trailingedge.com/misc/GCOS-GMAP-PocketGuide.pdf" rel="nofollow noreferrer">AOS (добавить единицу в хранилище) по крайней мере в 1967 году. не имеют аналогичной операции вычитания, и ни PDP- 7, где B идет дальше. К тому времени, как C столкнулся с PDP-11, команда DEC внедрила автоматически режимы адресации. - person bishop; 25.09.2016
comment
Учитывая, что добавление единицы к памяти было актуально еще в 1967 г., а режимы полной адресации стали доступны примерно после 1970 г., я подозреваю, что общественное мнение признавало ценность этих операций как на низком уровне (наборы инструкций), так и на высоком. уровень (В, С). - person bishop; 25.09.2016
comment
Этот связанный ответ содержит более длинный отрывок из книги «Развитие языка C». - person Keith Thompson; 26.09.2016

Когда вы отсчитываете от n, очень важно, идет ли речь до декремента или после декремента.

#include <stdio.h>
void foopre(int n) {
    printf("pre");
    while (--n) printf(" %d", n);
    puts("");
}
void foopost(int n) {
    printf("post");
    while (n--) printf(" %d", n);
    puts("");
}
int main(void) {
    foopre(5);
    foopost(5);
    return 0;
}

См. код, работающий в ideone.

person pmg    schedule 25.05.2015
comment
Спасибо, что поделились этой мудростью. Добавил ссылку на ваш пост. Скорее всего, мое предположение о том, что разница между обеими версиями очевидна, было неверным, и оно каким-то образом было перечеркнуто в моем ответе. - person mikyra; 25.05.2015

Чтобы получить ответ, выходящий за рамки предположений, скорее всего, вам придется лично спросить Денниса Ритчи и др.

Добавляя к уже данному ответу, я хотел бы добавить две возможные причины, которые я придумал:

  • лень/экономия места:

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

  • эстетика

    Из соображений симметрии наличие только одной версии до или после инкремента/декремента может показаться упущенным.

РЕДАКТИРОВАТЬ: добавлено резервирование нескольких байтов во входном файле в разделе предположений, что теперь также дает довольно приятную «историческую» причину.

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

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

person mikyra    schedule 25.05.2015
comment
К сожалению, чтобы спросить Денниса, вам понадобится доска для спиритических сеансов — он скончался 4 года назад. - person Charlie Martin; 25.05.2015
comment
Микира, сдай свой ботанический значок. Далее вы скажете нам, что не знали о Леонарде Нимое :-) - person paxdiablo; 25.05.2015
comment
И Джон Нэш... (Но во время создания C (ну, на самом деле, B) лень, возможно, не была проблемой, но сохранение нескольких драгоценных байтов во входном файле могло быть.) - person Jongware; 25.05.2015
comment
... как насчет Элвиса? Не говорите мне... в любом случае, хороший момент, я добавлю это к предположениям. - person mikyra; 25.05.2015
comment
... в любом случае, главное было продемонстрировать причины, которые могут быть не слишком историческими, поскольку по обеим причинам я все равно пропустил бы эту функцию сегодня и скорее признал бы язык странным, который их не поддерживает. - person mikyra; 25.05.2015

Для C

Давайте посмотрим на первоначальное обоснование Kernighan & Ritchie (оригинальный K&R, стр. 42 и 43):

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

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

Три приведенных примера (squeeze(), getline() и strcat()) используют только постфикс в выражениях с использованием индексации. Авторы сравнивают код с более длинной версией, в которой не используются встроенные приращения. Это подтверждает, что основное внимание уделяется компактности.

K&R выделит на стр. 102 использование этих операторов в сочетании с разыменованием указателя (например, *--p и *p--). Дальнейших примеров не приводится, но опять же, они ясно дают понять, что преимуществом является компактность.

Для C++

Бьерн Страуструп хотел иметь совместимость с C, поэтому C++ унаследовал приращение и декремент префикса и постфикса.

Но это еще не все: в своей книге «Дизайн и эволюция C++» Страуструп объясняет, что изначально он планировал иметь только одну перегрузку для постфикса и префикса в определяемых пользователем классах:

Несколько человек, в частности Брайан Керниган, указали, что это ограничение было неестественным с точки зрения C и не позволяло пользователям определять класс, который можно было бы использовать в качестве замены обычного указателя.

Это заставило его найти текущую разницу в подписи, чтобы различать префикс и постфикс.

Кстати, без этих операторов C++ был бы не C++, а C_plus_1 ;-)

person Christophe    schedule 26.09.2016

Рассмотрим следующий цикл:

for(uint i=5; i-- > 0;)
{
    //do something with i,
    // e.g. call a function that _requires_ an unsigned parameter.
}

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

Гораздо более серьезная проблема заключается в следующем: можно перегрузить операторы приращения (все 4) для класса. Но тогда операторы существенно отличаются: почтовые операторы обычно приводят к созданию временной копии экземпляра класса, а преоператоры - нет. Это огромная разница в семантике.

person David I. McIntosh    schedule 26.09.2016

У PDP-11 была одна инструкция, которая соответствовала *p++, и еще одна для *--p (или, возможно, наоборот).

person user207421    schedule 25.05.2015
comment
См. Развитие языка C (Ритчи). Эти операторы были в B, который был до PDP-11. - person dyp; 25.05.2015