C для цикла реализованы иначе, чем другие языки?
Я прочитал следующее в обзоре Кнута "Искусство компьютерного программирования":
"Сама" практичность "означает, что потенциальный CS-майор должен изучать ошибки Кернигана при проектировании C, в частности, печально известный факт, что цикл for обрабатывает условие for для неоднократного повторения, которое дублирует и не соответствует поведению большинство других языков, которые реализуют цикл for."
(http://www.amazon.com/review/R9OVJAJQCP78N/ref=cm_cr_pr_viewpnt#R9OVJAJQCP78N)
О чем этот парень говорит? Как вы могли реализовать цикл for, который был не просто синтаксическим сахаром для цикла while?
Ответы
Ответ 1
Рассмотрим это:
for i:=0 to 100 do { ... }
В этом случае мы могли бы заменить окончательное значение 100 на вызов функции:
for i:=0 to final_value() do { ... }
... и final_value
-функция будет вызываться только один раз.
В C, однако:
for (int i=0; i<final_value(); ++i) // ...
... final_value
-функция будет вызываться для каждой итерации по петле, что делает ее хорошей практикой более сложной:
int end = final_value();
for (int i=0; i<end; ++i) // ...
Ответ 2
Если все, что вам нужно, это простой цикл подсчета, то
for (i=0; i<100; i++) dostuff();
будет хорошо, и компилятор сможет его оптимизировать.
Если вы используете функцию в части продолжения оператора for, например
for (i=0; i<strlen(s); i++) dostuff();
тогда функция будет оцениваться каждый раз, и это, как правило, не очень хорошая идея, поскольку накладные расходы функции замедляют ваш процесс. Иногда это может замедлить ваш процесс до степени непригодности.
Если значение возвращаемой функции не изменится во время итерации, извлеките ее из цикла:
slen = strlen(s);
for (i=0; i<slen; i++) dostuff();
Но бывают случаи, когда функция будет возвращать разные значения для каждого вызова, а затем вы не хотите, чтобы она извлекалась из цикла:
for (isread(fd, &buffer, ISFIRST);
isstat(fd) >= 0;
isread(fd, &buffer, ISNEXT)
{
dostuff(buffer);
}
и вы хотите, чтобы он оценивался каждый раз. (Это немного надуманный пример, основанный на работе, которую я делаю, но он показывает потенциал).
C дает вам возможность откатить ваш цикл любым способом. Вы должны знать, как должен работать ваш цикл, и оптимизировать его, насколько сможете, в зависимости от ваших потребностей.
Этот последний пример мог бы быть выражен как цикл while:
isread(fd, &buffer, ISFIRST);
while (isstat(fd) >= 0)
{
dostuff(buffer);
isread(fd, &buffer, ISNEXT);
}
но это не так аккуратно, и если я использую продолжение в цикле, тогда я должен снова вызвать итерирование. Помещение всего объекта в цикл for делает его более аккуратным и гарантирует, что итерационный isread называется каждым циклом.
Я пишу функции нижнего уровня, поэтому их можно использовать для таких циклов. Он объединяет все элементы цикла while, чтобы вы могли легче понять это.
Ответ 3
Он, вероятно, ссылается на циклы типа for i:=0 to N
и for-each, которые перебирают элементы набора. Я подозреваю, что все языки, на которых есть цикл C-стиля, фактически получили его от C.
Ответ 4
Магнус имеет это право, но следует также отметить, что в большинстве языков (pre-C) условным является конечный критерий (т.е. "стоп, когда я равно 100" ). В C (и большинстве пост-C-языков) это критерий продолжения (т.е. "Продолжать, а я меньше 100" ).
Ответ 5
Возможно, развернется петля? Если вы знаете, сколько раз цикл for будет выполняться, вы можете буквально скопировать и вставить содержимое цикла. В большинстве случаев петли будут основываться на некоторых условиях, которые не являются простым подсчетом от 0 до N, поэтому не смогут использовать эту оптимизацию.
Возьмем этот пример
int x;
for (x = 10; x != 0; --x)
{
printf ("Hello\n");
}
Я знаю, что вы обычно делаете x = 0; x <= 10; ++x
, но все будет обнаружено в сборке.
некоторая псевдо-сборка:
mov 10, eax
loop:
print "hello"
dec eax
jne loop
В этом примере мы продолжаем прыгать назад вокруг цикла, чтобы напечатать "привет" 10 раз. Однако мы оцениваем условие с инструкцией jne
каждый раз вокруг цикла.
Если мы развернули его, мы могли бы просто положить
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
Нам не нужны никакие другие инструкции, так что это быстрее. Это всего лишь простой пример - я постараюсь найти лучший!
Ответ 6
В принципе, C (и Java, JavaScript и много C-производных языков) for
цикл действительно синтаксический сахар для цикла while
:
for (int i = 0; i < max; i++) { DoStuff(); }
который является очень текущей идиомой, строго эквивалентен:
int i = 0; while (i < max) { DoStuff(); i++; }
(Разделяя проблемы с областью, которые в любом случае изменялись между версиями C.)
Условие остановки оценивается на каждой итерации. В некоторых случаях это может быть интересно, но это может быть ловушка: я видел i < strlen(longConstantString)
в источнике, что является основным способом замедления работы программы, потому что strlen
является дорогостоящей функцией в C.
Как бы то ни было, цикл for
в основном предназначен для многократного использования заранее, вы все равно можете использовать break
для завершения на ранней стадии, поэтому динамическая оценка термина термина более раздражает, чем полезно: если вам действительно нужна динамическая вы используете while () {}
(или do {} while ()
).
На некоторых других языках, таких как Lua, условие остановки оценивается только один раз, в цикле init. Он помогает прогнозировать поведение цикла, и он часто более эффективен.
Ответ 7
На x86, для циклирования можно выполнить на уровне сборки, не создавая цикл while. Инструкция loop уменьшает значение регистра ecx и переходит к операнду, если ecx больше 0, он ничего не делает. Это классика для цикла.
mov ecx, 0x00000010h
loop_start:
;loop body
loop loop_start
;end of loop
Ответ 8
В Ada (и я считаю, что большинство других языков, порожденных Алголом) условие завершения цикла "for" оценивается только один раз, в начале цикла. Например, предположим, что у вас есть следующий код в Ada
q := 10;
for i in 1..q loop
q := 20;
--// Do some stuff
end loop;
Этот цикл будет повторяться ровно 10 раз, потому что q было 10, когда цикл был запущен. Однако, если я напишу кажущийся эквивалентный цикл в C:
q = 10;
for (int i=0;i<q;i++) {
q = 20;
// Do some stuff
}
Затем цикл повторяется 20 раз, потому что к тому времени, когда я получил достаточно большое значение, значение q было изменено на 20.
Конечно, C-путь более гибкий. Однако это имеет немало негативных последствий. Очевидным является то, что программе приходится тратить усилия на повторную проверку условия цикла в каждом цикле. Хороший оптимизатор может быть достаточно умным, чтобы обойти проблему в простых случаях, подобных этому, но что произойдет, если "q" является глобальным и "сделать что-то" включает вызов процедуры (и, следовательно, теоретически может изменить q)?
Жесткий факт заключается в том, что мы просто знаем больше о цикле Ada, чем о цикле C. Это означает, что с таким же уровнем интеллекта и усилий в своем оптимизаторе Ada может значительно улучшить оптимизацию. Например, компилятор Ada знает, что он может заменить весь цикл на 10 копий содержимого независимо от того, что это такое. Оптимизатор C должен будет исследовать и анализировать содержимое.
На самом деле это всего лишь один из многих способов, в которых дизайн синтаксиса C блокирует компилятор.
Ответ 9
То, что этот пурист языка никогда не понимает, - это весь смысл C, и в некоторой степени С++ дает возможность реализовать то, что вы хотите и как вы хотите. Теперь, по общему признанию, если результат вашего конечного выражения изменится, было бы лучше просто использовать цикл while. Возможно, проблема заключается в том, что программисты получают впечатление, что C реализует "высокий уровень", но каждый должен знать, что C - довольно низкоуровневый язык.
Ответ 10
Сама "практичность" означает, что потенциальный главный специалист CS должен научиться ошибкам Кернигана при проектировании C, в частности, печально известный факт, что цикл for обрабатывает неоднозначно условие, которое дублирует и не соответствует поведению большинства другие языки, которые реализуют цикл for.
Muaahahahaa!
Мне нравятся основные (pun intented) утверждения рецензента:
- Ошибки Кернигана
- печально известный факт
- не соответствует поведению большинства других языков
Для меня это пахнет тем, кто никогда не преуспевал в овладении базовыми функциями C/philosphy.
Введение
Поскольку я еще изучал физику в университете, я обнаружил, что C (то есть C-подобные языки) должны были стать моим языком выбора, когда я обнаружил цикл C для.
Таким образом, по-видимому, одна неудача стиля - это еще один успех стиля. Это закончит субъективную часть этого обсуждения.
Возможно, кернигану нужно было бы назвать loop?
Просто шучу? Возможно, нет.
Автор обзора, по-видимому, решил, что каждая языковая конструкция должна иметь одинаковое поведение на разных языках. Поэтому переименование для, поскольку цикл ослабит его/ее дискомфорт.
Проблема заключается в том, что автор не понимает, что C для является расширением , а, а не другим циклом. Это означает , а - синтаксическая версия с сахаром для, вместо в то время как другие языки, на которых для были кастрированы вниз.
Действительно ли C нужен?
Процитировав автора обзора, он делает следующие утверждения о для и , а:
- для - для постоянных циклов, от начала до конца
- , а - для циклов, оценивающих условие на каждой итерации
Наличие ортогональных функций - хорошая вещь, когда вы можете их комбинировать. Но на всех языках, которые я знаю, нет возможности объединить как для, так и , а.
Пример: что, если для языка рецензента требуется цикл, идущий от начала до конца (например, для), но все же способный оценивать на каждой итерации цикла (например, < в то время как): вам может понадобиться найти в подмножестве контейнера (не весь контейнер) значение, соответствующее некоторому тесту проверки состояния?
В C-подобном языке вы используете для. В идеальном рецензентном языке вы просто взломаете какой-то уродливый код, плача, потому что у вас нет цикла for_while (или C для).
Заключение
Я считаю, что мы можем обобщить критику рецензента C (и C-подобных языков) со следующим утверждением: "Керниган печально известная ошибка, не дающая C синтаксиса предыдущих, существующих языков", который можно свести к следующему: "Никогда думайте иначе".
Цитата Бьярне Страуструпа:
Существует только два вида языков: одни люди жалуются и те, кого никто не использует.
Итак, в целом, комментарий рецензента следует рассматривать как похвалу.
^ _ ^
Ответ 11
Возможно, Кнут ссылается на BASIC.
В BASIC (более старый, я не знаю о VB), FOR цикла были реализованы по-разному.
например. зацикливать десять раз
ДЛЯ N = 1 до 10
...
NEXT N
---- ИЛИ ----
найти сумму четных чисел ниже 100
ДЛЯ N = 0 - 100 ШАГ 2
...
NEXT N
В C у вас может быть какое-либо условие в "для", чтобы проверить выход из цикла. Я думаю, более гибкий.