printf - источник ошибок? [закрыто]

9

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

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

Некоторые забирают очки

  • В настоящее время я считаю, что безопасность типов перевешивает любые преимущества использования printf. Настоящая проблема - это строка формата и использование не типизированных переменных функций.
  • Может быть, я не буду использовать <<и варианты потока вывода stl, но я непременно расскажу об использовании механизма защиты типов, который очень похож.
  • Большая часть трассировки / ведения журнала является условной, но я бы хотел всегда запускать код, чтобы не пропустить ошибки в тестах только потому, что это редко используемая ветвь.
Джон Лейдгрен
источник
4
printfв мире C ++? Я что-то здесь упускаю?
user827992
10
@ user827992: Вам не хватает того факта, что стандарт C ++ включает стандартную библиотеку C по ссылке? Это совершенно законно использовать printfв C ++. (Является ли это хорошей идеей, это другой вопрос.)
Кит Томпсон
2
@ user827992: printfимеет некоторые преимущества; смотри мой ответ.
Кит Томпсон
1
Этот вопрос довольно пограничный. «Что вы думаете, ребята» вопросы часто закрыты.
dbracey
1
@ Vitaut Я думаю (спасибо за совет). Я просто немного озадачен агрессивной модерацией. Это на самом деле не способствует интересным дискуссиям о программировании ситуаций, что я хотел бы иметь больше.
Джон Лейдгрен,

Ответы:

2

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

Да, чтобы выяснить формат нашего вывода, то, что мы можем знать на 100% во время компиляции, давайте проанализируем измененную строку формата во время выполнения внутри очень странной таблицы переходов, используя непостижимые коды формата!

Конечно, эти коды форматов нельзя было сделать так, чтобы они соответствовали типам, которые они представляют, это было бы слишком просто ... и вам будет напоминаться каждый раз, когда вы просматриваете, является ли% llg или% lg тем, что этот (строго типизированный) язык делает вас вычислять типы вручную, чтобы что-то печатать / сканировать, И был разработан для пред-32-битных процессоров.

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

(Если вы вообще используете стандартные контейнеры, напишите себе несколько быстрых шаблонных операторов << overloads, которые позволяют вам делать такие вещи, как std::cout << my_list << "\n";для отладки, где my_list имеет тип list<vector<pair<int,string> > >.)

jkerian
источник
1
Проблема стандартной библиотеки C ++ состоит в том, что большинство воплощений реализуется operator<<(ostream&, T)путем вызова ... ну sprintf,! Производительность sprintfне оптимальна, но из-за этого производительность iostreams, как правило, еще хуже.
Ян Худек
@JanHudec: Это не было правдой около десяти лет на данный момент. Фактическая печать выполняется с теми же базовыми системными вызовами, и реализации C ++ часто вызывают для этого библиотеки C ... но это не то же самое, что маршрутизация std :: cout через printf.
jkerian
16

Смешивание вывода в стиле C printf()(или puts()или putchar()или ...) с выводом в стиле C ++ std::cout << ...может быть небезопасным. Если я правильно помню, у них могут быть отдельные механизмы буферизации, поэтому выходные данные могут не отображаться в намеченном порядке. (Как AProgrammer упоминает в комментарии, sync_with_stdioобращается к этому).

printf()принципиально небезопасен. Тип, ожидаемый для аргумента, определяется форматной строкой ( "%d"требуется символ intили что-то, что повышается int, "%s"требуется элемент, char*который должен указывать на правильно завершенную строку в стиле C и т. Д.), Но передача неверного типа аргумента приводит к неопределенному поведению , не диагностируемая ошибка. Некоторые компиляторы, такие как gcc, достаточно хорошо предупреждают о несоответствиях типов, но они могут делать это только в том случае, если строка формата является литералом или иным образом известна во время компиляции (что является наиболее распространенным случаем) - и так далее. предупреждения не требуются языком. Если вы передадите неправильный тип аргумента, может произойти сколь угодно плохое.

С другой стороны, потоковый ввод / вывод в C ++ намного более безопасен для типов, поскольку <<оператор перегружен для множества различных типов. std::cout << xне нужно указывать тип x; компилятор сгенерирует правильный код для любого типа x.

С другой стороны, printfпараметры форматирования ИМХО гораздо удобнее. Если я хочу напечатать значение с плавающей точкой с 3 цифрами после десятичной точки, я могу использовать "%.3f"- и это не влияет на другие аргументы, даже в пределах того же самого printfвызова. С ++ setprecision, с другой стороны, влияет на состояние потока и может испортить последующий вывод, если вы не очень осторожны, чтобы вернуть поток в его предыдущее состояние. (Это моя личная любимая мозоль; если мне не хватает какого-то чистого способа избежать этого, пожалуйста, прокомментируйте.)

Оба имеют свои преимущества и недостатки. Доступность printfособенно полезна, если у вас есть фон C и вы с ним более знакомы, или если вы импортируете исходный код C в программу C ++. std::cout << ...более идиоматичен для C ++ и не требует особого внимания, чтобы избежать несоответствия типов. Оба являются действительными C ++ (стандарт C ++ включает в себя большую часть стандартной библиотеки C по ссылке).

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

И, конечно, стоит потратить некоторое время на изучение того, как использовать отладчики (но в некоторых средах это может оказаться невозможным).

Кит Томпсон
источник
Нет упоминания о микшировании в оригинальном вопросе.
dbracey
1
@dbracey: Нет, но я подумал, что стоит упомянуть в качестве возможного недостатка printf.
Кит Томпсон
6
Для проблемы синхронизации, см std::ios_base::sync_with_stdio.
AProgrammer
1
+1 Использование std :: cout для печати отладочной информации в многопоточном приложении бесполезно на 100%. По крайней мере, с printf вещи не так вероятны для чередования и разборчивости человеком или машиной.
Джеймс
@James: Это потому, что std::coutиспользует отдельный вызов для каждого печатного элемента? Вы можете обойти это, собрав строку вывода в строку перед печатью. И, конечно, вы также можете печатать по одному элементу за раз printf; просто удобнее напечатать строку (или больше) за один звонок.
Кит Томпсон
2

Скорее всего, ваша проблема возникла из-за смешения двух совершенно разных стандартных менеджеров вывода, у каждого из которых есть своя повестка дня для этого бедного маленького STDOUT. Вы не получаете никаких гарантий относительно того, как они реализованы, и вполне возможно, что они устанавливают конфликтующие параметры дескриптора файла, оба пытаются делать с ним разные вещи и т. Д. Кроме того, операторы вставки имеют одну важную особенность printf: printfпозволит вам сделать это:

printf("%d", SomeObject);

Тогда как <<не будет.

Примечание: для отладки вы не используете printfили cout. Вы используете fprintf(stderr, ...)и cerr.

Linuxios
источник
Нет упоминания о микшировании в оригинальном вопросе.
dbracey
Конечно, вы можете напечатать адрес объекта, но большая разница в том, что printfон не безопасен для типов, и я считаю, что безопасность типов перевешивает любые преимущества использования printf. Проблема на самом деле в строке формата и не типизированной переменной функции.
Джон Лейдгрен
@JohnLeidegren: Но что, если SomeObjectэто не указатель? Вы собираетесь получить произвольные двоичные данные, которые решает представлять компилятор SomeObject.
Linuxios
Я думаю, что я прочитал ваш ответ в обратном направлении ... НВМ.
Джон Лейдгрен
1

Есть много групп, например Google, которым не нравятся потоки.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Откройте треугольник, чтобы вы могли увидеть обсуждение.) Я думаю, что в руководстве по стилю Google C ++ есть много очень разумных советов.

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

dbracey
источник
2
Руководство по стилю Google приятно, НО в нем довольно много элементов, которые не подходят для общего руководства . (это нормально, потому что, в конце концов, это руководство Google по коду, запущенному в / для Google.)
Мартин Ба,
1

printfможет вызвать ошибки из-за отсутствия безопасности типа. Есть несколько способов адресации , которые не переключаясь на iostream«s <<оператор и более сложное форматирование:

  • Некоторые компиляторы (такие как GCC и Clang) могут при желании проверять printfстроки формата по printfаргументам и отображать предупреждения, такие как следующие, если они не совпадают.
    предупреждение: преобразование указывает тип 'int', но аргумент имеет тип 'char *'
  • Сценарий typesafeprintf может предварительно обработать printfвызовы -style, чтобы сделать их безопасными для типов.
  • Библиотеки, такие как Boost.Format и FastFormat, позволяют использовать printfстроки формата, похожие на форматы (в частности, Boost.Format практически идентичны printf), сохраняя при этом iostreams«безопасность типов и расширяемость типов».
Джош Келли
источник
1

Синтаксис printf в основном хорош, за исключением некоторых непонятных типов. Если вы считаете, что это неправильно, почему C #, Python и другие языки используют очень похожую конструкцию? Проблема в C или C ++: он не является частью языка и, следовательно, не проверяется компилятором на предмет правильного синтаксиса (*) и не разбивается на серии собственных вызовов при оптимизации по скорости. Обратите внимание, что при оптимизации по размеру вызовы printf могут оказаться более эффективными! Синтаксис потокового C ++ imho совсем не хорош. Это работает, безопасность типов есть, но подробный синтаксис ... блеф. Я имею в виду, я использую это, но без радости.

(*) некоторые компиляторы выполняют эту проверку плюс почти все инструменты статического анализа (я использую Lint и с тех пор у меня никогда не было проблем с printf).

MaR
источник
1
Существует Boost.Format, который сочетает в себе удобный синтаксис ( format("fmt") % arg1 % arg2 ...;) с безопасностью типов. За счет некоторой дополнительной производительности, поскольку он генерирует вызовы stringstream, которые внутренне генерируют вызовы sprintf во многих реализациях.
Ян Худек
0

printfПо моему мнению, это гораздо более гибкий инструмент вывода для работы с переменными, чем любой из результатов потока CPP. Например:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Однако вам может потребоваться использовать <<оператор CPP, когда вы перегружаете его для определенного метода ... например, чтобы получить дамп объекта, который содержит данные конкретного человека, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Для этого было бы гораздо эффективнее сказать (если предположить, aчто это объект PersonData)

std::cout << a;

чем:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

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

Aviator45003
источник
0

Вы не должны использовать printfв C ++. Когда-либо. Причина, как вы правильно заметили, в том, что это источник ошибок и тот факт, что печать пользовательских типов, а в C ++ почти все должны быть пользовательскими типами, - это боль. C ++ решение - это потоки.

Однако существует критическая проблема, которая делает потоки непригодными для любого и видимого пользователю вывода! Проблема в переводе. Взяв пример из руководства по gettext , вы хотите написать:

cout << "String '" << str << "' has " << str.size() << " characters\n";

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

n Zeichen lang ist die Zeichenkette ' s '

А теперь у вас неприятности, потому что ему нужны перемешанные кусочки. Следует сказать, что даже многие реализации printfимеют проблемы с этим. Если они не поддерживают расширение, чтобы вы могли использовать

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

В Boost.Format поддерживает форматы PRINTF стиля и имеет эту функцию. Итак, вы пишете:

cout << format("String '%1' has %2 characters\n") % str % str.size();

К сожалению, он несет в себе некоторое снижение производительности, поскольку внутренне он создает поток строк и использует <<оператор для форматирования каждого бита, а во многих реализациях <<оператор вызывает его внутренне sprintf. Я подозреваю, что более эффективная реализация была бы возможна, если бы действительно желательно

Ян Худек
источник
-1

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

Просто используйте отладчик и прочитайте что-нибудь об Исключениях и о том, как их перехватить и выбросить; постарайтесь не быть более многословным, чем на самом деле.

PS

printf используется в C, для C ++ у вас есть std::cout

user827992
источник
Вы не используете трассировку / журналирование вместо отладчика.
Джон Лейдгрен