Spec-Zone.ru › Perl 5.38

perlhacktips

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • ОБЩИЕ ПРОБЛЕМЫ
    • Проблемы среды Perl
    • C99
    • Имена символов и загрязнение пространства имен
      • Выбор законных имён символов
      • Выбор хороших имён символов
    • Написание более безопасных макросов
    • Проблемы переносимости
    • Проблемные системные интерфейсы
    • Проблемы безопасности
  • ОТЛАДКА
    • Работа с Perl
    • Использование отладчика на уровне исходного кода
    • Поддержка макросов gdb
    • Вывод структур данных Perl
    • Использование gdb для просмотра определённых частей программы
    • Использование gdb для просмотра того, что делают парсер/лексер
  • СТАТИЧЕСКИЙ АНАЛИЗ ИСТОЧНИКА
    • lint
    • Coverity
    • HP-UX cadvise (Консультант по коду)
    • cpd (детектор копирования и вставки)
    • Предупреждения gcc
    • Предупреждения других компиляторов C
  • ОТЛАДЧИКИ ПАМЯТИ
    • valgrind
    • AddressSanitizer
  • ПРОФИЛИРОВАНИЕ
    • Профилирование Gprof
    • Профилирование GCC gcov
    • Профилирование callgrind
  • РАЗНООБРАЗНЫЕ ХИТРОСТИ
    • PERL_DESTRUCT_LEVEL
    • PERL_MEM_LOG
    • DDD через gdb
    • C-обратный трассировка
    • Яд
    • Только для чтения optree
    • Когда bool не является bool?
    • Поиск небезопасных усечений
    • Цели .i
  • АВТОР

НАЗВАНИЕ

perlhacktips - Советы по взлому Perl кода на C

ОПИСАНИЕ

Этот документ поможет вам изучить лучшие способы взлома кода Perl на C. Он охватывает общие проблемы, отладку, профилирование и многое другое.

Если вы ещё не читали perlhack и perlhacktut, возможно, стоит сделать это сначала.

ОБЩИЕ ПРОБЛЕМЫ

Исходный код Perl теперь допускает некоторые специфические особенности C99, которые, как известно, поддерживаются всеми платформами, но в основном подчиняется правилам ANSI C89. Вам всё равно, что какая-то платформа имеет неисправный Perl? Мне сказали, что всё ещё есть сильный спрос на программистов J2EE.

Проблемы среды Perl

  • Не компилируется с потоками

    Компиляция с потоками (-Duseithreads) полностью переписывает прототипы функций Perl. Лучше попробовать свои изменения с этим. Связанным с этим является различие между API "Perl_-less" и "Perl_-ly", например:

    Perl_sv_setiv(aTHX_ ...);
    sv_setiv(...);

    В первом явным образом передаётся контекст, который необходим, например, при многопоточных сборках. Во втором это делается неявно; не смешивайте их. Если вы не передаёте aTHX_, вам нужно выполнить dTHX как первое действие в функции.

    См. "Как поддерживаются несколько интерпретаторов и параллельность" в perlguts для дальнейшего обсуждения контекста.

  • Не компилируется с -DDEBUGGING

    Определение DEBUGGING предоставляет компилятору больше кода, следовательно, больше способов, которыми могут возникнуть проблемы. Вы должны это попробовать.

  • Введение (не только для чтения) глобальных переменных

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

    Введение только для чтения (const) глобальных переменных допустимо, если вы проверите, например, nm libperl.a|egrep -v ' [TURtr] ' (если ваш nm имеет вывод в стиле BSD), что добавленные данные действительно только для чтения. (Если это так, то он не должен отображаться в выводе этой команды.)

    Если вы хотите иметь статические строки, сделайте их постоянными:

    static const char etc[] = "...";

    Если вы хотите иметь массивы постоянных строк, внимательно обратите внимание на правильную комбинацию const:

    static const char * const yippee[] =
        {"hi", "ho", "silver"};
  • Функция не экспортирована

    Некоторые платформы (Win32, AIX, VMS, OS/2 и другие) требуют, чтобы любая функция, которая является частью публичного API (общая библиотека Perl), была явно помечена как экспортируемая. См. обсуждение embed.pl в perlguts.

  • Экспорт новой функции

    Результат нового функционала или вашего усердного рефакторинга теперь готов и правильно экспортирован. Так что же может пойти не так?

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

    Если функция используется только внутри одного файла исходного кода, сделайте её статической. См. обсуждение embed.pl в perlguts.

    Если функция используется в нескольких файлах, но предназначена только для внутреннего использования Perl (и это должен быть общий случай), не экспортируйте её в общедоступный API. См. обсуждение embed.pl в perlguts.

C99

Начиная с версии 5.35.5, в ядре C Perl разрешены некоторые особенности C99. Однако код в расширениях с двойной жизнью по-прежнему должен быть только C89, потому что ему нужно компилироваться с более ранними версиями Perl, работающими на старых платформах. Также обратите внимание, что наши заголовки также должны быть валидны как C++, так как расширения XS, написанные на C++, должны их включать, поэтому инициализаторы структур членов не могут использоваться в заголовках.

Поддержка C99 всё ещё неполная на всех платформах, которые мы поддерживаем. В качестве базового значения мы можем только предположить семантику C89 с конкретными функциями C99, которые, как мы проверили, работают везде. Вполне допустимо исследовать дополнительные функции C99 и использовать их там, где доступно, при условии, что есть также откат для компиляторов, которые не поддерживают эту функцию. Например, мы используем C11 thread local storage, когда это доступно, но переходим к POSIX thread-специфичным API в противном случае, и мы используем char для булевых значений, если <stdbool.h> недоступно.

Код может использовать (и полагаться на) наличие следующих функций C99:

  • смешанные объявления и код

  • 64-битные целочисленные типы

    Для согласованности с существующим исходным кодом используйте определения типов I64 и U64, вместо прямого использования long long и unsigned long long.

  • макросы с переменным числом аргументов

    void greet(char *file, unsigned int line, char *format, ...);
    #define logged_greet(...) greet(__FILE__, __LINE__, __VA_ARGS__);

    Обратите внимание, что __VA_OPT__ — это расширение gcc, которое ещё не включено в какой-либо опубликованный стандарт.

  • объявления в циклах for

    for (const char *p = message; *p; ++p) {
        putchar(*p);
    }
  • инициализаторы структур членов

    Но не в заголовках, так как поддержка была добавлена в C++ относительно недавно.

    Поэтому это нормально в коде C и XS, но не в заголовках:

    struct message {
        char *action;
        char *target;
    };
    
    struct message mcguffin = {
        .target = "member structure initialisers",
        .action = "Built"
     };
  • гибкие члены массивов

    Это соответствует стандарту:

    struct greeting {
        unsigned int len;
        char message[];
    };

    Однако исходный код уже использует "чрезмерную близость с компилятором" в многих местах:

    struct greeting {
        unsigned int len;
        char message[1];
    };

    Строго говоря, обращение за пределы message[0] является неопределённым поведением, но это общепринятый трюк со времён K&R, и его использование не вызывало практических проблем нигде (в исходном коде perl или в любом другом распространённом коде C). Поэтому неясно, что мы выиграем от активного перехода к подходу C99.

  • // комментарии

    Все протестированные компиляторы поддерживают их использование. Не все протестированные люди поддерживают их использование.

Код явно не должен использовать другие функции C99. Например

  • массивы переменной длины

    Не поддерживается ни одним MSVC, и это не изменится.

    Даже массивы «переменной» длины, где переменная является константным выражением, являются синтаксическими ошибками в MSVC.

  • типы C99 в <stdint.h>

    Используйте PERL_INT_FAST8_T и т. д., как определено в handy.h

  • строки формата C99 в <inttypes.h>

    snprintf в VMS libc только что добавили поддержку PRIdN и т. д. недавно, что означает наличие поддерживаемых установок без этого или форматов, таких как %zu.

    (sv_catpvf Perl и т. д. используют код парсера в sv.c, который поддерживает модификатор z, наряду со специфическими для Perl форматами, такими как SVf.)

Если вы хотите использовать функцию C99, не указанную выше, вам нужно сделать одно из следующих:

  • Исследовать её в Configure, установить переменную в config.sh и добавить откат в заголовки для платформ, которые её не поддерживают.

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

Вероятно, вы хотите повторить тот же план, что мы использовали для получения текущего набора функций C99. См. сообщение по адресу https://markmail.org/thread/odr4fjrn72u2fkpz для C99-зондов, которые мы использовали ранее. Обратите внимание, что двумя наиболее «требовательными» компиляторами являются MSVC и компилятор поставщика на VMS. На сегодняшний день все компиляторы *nix оказались гораздо более гибкими в том, что они поддерживают.

В платформах *nix, Configure пытается установить флаги компилятора должным образом. Все компиляторы поставщиков, которые мы тестировали, по умолчанию поддерживали C99 (или C11). Однако более старые версии gcc по умолчанию используют C89 или разрешают большинство функций C99 (с предупреждениями), но запрещают объявления в циклах for, если не добавлен -std=gnu99. Альтернатива -std=c99 может показаться лучше, но ее использование в некоторых платформах может препятствовать <unistd.h> объявить некоторые прототипы, что приводит к сбою сборки. Флаг -ansi gcc подразумевает -std=c89, поэтому мы больше не можем его устанавливать, поэтому опция Configure -gccansipedantic теперь добавляет только -pedantic.

Файлы исходного кода ядра Perl (файлы на верхнем уровне дистрибутива исходного кода) автоматически компилируются с максимально возможным количеством флагов -std=gnu99, -pedantic, и выборочным набором флагов -W (см. cflags.SH). Файлы в папках ext/, dist/, cpan/ и т. д. компилируются с теми же флагами, что использует установленный perl для компиляции расширений XS.

В принципе, можно считать, что Configure и cflags.SH выбрали наилучшее сочетание флагов для версии gcc в данной платформе, и попытка добавить больше флагов, связанных с принудительным применением диалекта C, вызовет проблемы либо локально, либо на других системах, куда будет распространяться код.

Мы считаем, что поддержка C99 в gcc 3.1 достаточна для нас, но у нас нет gcc 19-летней давности, чтобы проверить это :-) Если у вас есть старые компиляторы поставщиков, которые по умолчанию не поддерживают C99, флагами, которые вы можете попробовать, являются

AIX

-qlanglvl=stdc99

HP/UX

-AC99

Solaris

-xc99

Имена символов и загрязнение пространства имен

Выбор допустимых имен символов

C резервирует для своей реализации любые символы, имена которых начинаются с подчеркивания, сразу за которым следует заглавная буква [A-Z] или другое подчеркивание. C++ дополнительно резервирует любые символы, содержащие два последовательных подчеркивания, и дополнительно резервирует в глобальном пространстве имен любые символы, начинающиеся с подчеркивания, а не только те, за которыми следует заглавная буква. Мы заботимся о C++, потому что hdr файлы должны компилироваться им, и некоторые люди выполняют всю свою разработку с использованием компилятора C++.

Последствия нарушения этого правила, вероятно, отсутствуют. Если вы не наткнетесь на имя, используемое реализацией, всё будет работать. Действительно, ядро perl содержит немало примеров использования зарезервированных реализацией символов. (Эти символы постепенно изменяются.) Но ваш код может перестать работать в любой момент, когда реализация решит использовать имя, которое вы уже выбрали, возможно, много лет назад.

Лучше всего:

Не начинайте имя символа с подчеркивания; (например, не используйте: _FOOBAR)
Не используйте два последовательных подчеркивания в имени символа; (например, не используйте FOO__BAR)

POSIX также резервирует многие символы. См. раздел 2.2.2 в http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html. Perl также имеет конфликты с этим.

Perl резервирует для своего использования любые символы, начинающиеся с Perl, perl, или PL_. Всякий раз, когда вы вводите макрос в файл hdr, который не соответствует этой конвенции, вы создаёте возможность конфликта имён с существующим модулем XS, если только не ограничите его, скажем,

#ifdef PERL_CORE
#  define my_symbol
#endif

В файлах hdr содержится множество символов, не имеющих такого формата, и которые доступны из пространства имен XS, намеренно или нет, практически всё в config.h, например.

Приходится использовать один из этих префиксов, что снижает читаемость кода, и для нетривиальных имен это не было реальной проблемой. Такие вещи, как perl, определяющий свой собственный макрос MAX, создавали проблемы, но они быстро обнаруживались, и добавлялась #ifdef PERL_CORE защита.

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

Выбор хороших имён символов

В идеале, имя символа должно точно и чётко описывать его предназначение. Но существует противоречие между этим и получением имён, которые слишком длинные и неудобные для ввода и чтения. Метафоры могут быть полезны (поэтическое имя), но они часто зависят от культуры и могут не переводиться для человека, чей родной язык не английский или у кого иная культурная среда. Кроме того, талант к написанию стихов, кажется, редок у программистов.

Некоторые имена символов не отражают их назначение, но тем не менее их можно использовать из-за устоявшихся соглашений. Они часто происходят из математики, где i и j часто используются в качестве нижних индексов, а n — как счётчик элементов. С 1950-х годов компьютерные программы используют i, и т. д., в качестве переменных цикла.

Мы рекомендуем выбирать имя, разумно описывающее назначение, и комментировать объявление более подробно.

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

Возможно, остаётся много ошибок «+1» из-за того, что имя "av_len" в perlapi не соответствует значению других конструкций с суффиксом -len, таких как "sv_len" в perlapi. Были созданы неудобные (и противоречивые) синонимы, чтобы передать истинное значение ("av_top_index" в perlapi). В конце концов, кто-то придумал лучшее название для обозначения того, что большинство людей думают под -len. Так родилось "av_count" в perlapi. И мы бы хотели, чтобы об этом подумали раньше.

Написание более безопасных макросов

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

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

Тем не менее, существуют ситуации, когда функция не подойдёт, и требуется макрос. Один пример — когда параметр может быть одного из нескольких типов. Функции должны быть объявлены с одним явным

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

Если вы всё-таки решите использовать нетривиальный макрос, имейте в виду несколько избегаемых ловушек, которые могут возникнуть. Помните, что макрос расширяется в лексическом контексте каждого места в исходном коде, где он вызывается. Если у вас есть токен foo в макросе, и исходный код также содержит foo, значение макроса foo примет значение вызывающей стороны. Иногда именно этого поведения вы и хотите, но будьте осторожны, что это часто вводит в заблуждение позднее. Это фактически превращает foo в зарезервированное слово для любого кода, который вызывает макрос, и этот факт обычно не документируется и не рассматривается. Безопаснее передать foo в качестве параметра, чтобы foo оставалась свободно доступной для вызывающей стороны, и интерфейс макроса был явно указан.

Хуже, когда эквивалентность двух foo совпадает случайно. Предположим, например, что макрос объявляет переменную

int foo

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

#define foo  bar

Тогда объявление foo в макросе внезапно становится

int bar

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

Поэтому, если макрос использует переменные, их имена должны быть такими, чтобы они с очень малой вероятностью совпадали с именем любой вызывающей стороны, сейчас или когда-либо. Один способ сделать это, который сейчас используется в исходном коде perl, заключается в включении имени самого макроса в имя каждой переменной в макросе. Предположим, что макрос называется SvPV. Тогда мы можем иметь

int foo_svpv_ = 0;

Это труднее читать, чем просто foo, но практически гарантируется, что вызывающая сторона никогда не будет использовать foo_svpv_ (и столкнется с проблемами). (Приведение к нижнему регистру делает более явным, что это переменная, но предполагает, что не будет двух элементов, чьи имена различаются только регистром символов.) Завершающее подчеркивание делает столкновение ещё менее вероятным, поскольку по соглашению они обозначают имя приватной переменной. (См. "Выбор допустимых имён символов" для ограничений на используемые имена.)

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

Выше мы сказали «сначала сделайте копию». В макросе это легче сказать, чем сделать, потому что макросы обычно являются выражениями, а объявления недопустимы в выражениях. Но конструкция STMT_START .. STMT_END, описанная в perlapi, позволяет иметь объявления в большинстве контекстов, пока вам не нужен возвращаемый результат. Если вам нужен возвращаемый результат, вы можете создать интерфейс таким образом, чтобы указатель передавался в конструкцию, которая затем хранит результат в нём. (Или вы можете использовать группы фигурных скобок GCC. Но эти требуют резервного варианта, если код когда-либо будет выполняться на платформе, где отсутствует это нестандартное расширение для C. И этот резервный вариант будет другим кодовым путем, который может выйти из синхронизации с группой фигурных скобок, поэтому делать это не рекомендуется.) В ситуациях, где нет другого способа, Perl предоставляет "PL_Sv" в perlintern и "PL_na" в perlapi для использования (с небольшой потерей производительности) для некоторых таких распространённых случаев. Но имейте в виду, что цепочка вызовов, включающая несколько макросов, использующих их, уничтожит использование других. Эти проблемы были очень сложными для отладки.

Для конкретного примера таких подводных камней в действии см. https://perlmonks.org/?node_id=111444355

Проблемы портативности

Следующие являются распространёнными причинами сбоев компиляции и/или выполнения, не характерными для Perl как такового. Часто задаваемые вопросы по C — хорошая книга для вечернего чтения. Пожалуйста, протестируйте свои изменения с как можно большим количеством компиляторов C и платформ; мы, в любом случае, сделаем это, и приятно сохранить себя от публичного позора.

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

Не предполагайте, что операционная система указывает на определённый компилятор.

  • Приведение указателей к целым числам или приведение целых чисел к указателям

    void castaway(U8* p)
    {
      IV i = p;

    или

    void castaway(U8* p)
    {
      IV i = (IV)p;

    Оба варианта плохи, неисправны и непереносимы. Используйте макрос PTR2IV(), который выполняет эту операцию правильно. (Аналогично, существуют макросы PTR2UV(), PTR2NV(), INT2PTR() и NUM2PTR()).

  • Приведение указателей на функции к указателям на данные

    Технически, приведение указателей на функции к указателям на данные непереносимо и неопределенно, но на практике кажется, что это работает. Однако вы должны использовать макросы FPTR2DPTR() и DPTR2FPTR(). Иногда можно также использовать объединения.

  • Предположение, что sizeof(int) == sizeof(long)

    Существуют платформы, где long — 64 бита, и платформы, где int — 64 бита, и, чтобы вас шокировать, даже платформы, где short — 64 бита. Всё это законно согласно стандарту C. (Другими словами, «long long» — не переносимый способ указать 64 бита, и «long long» даже не гарантируется, что он будет шире, чем «long».)

    Вместо этого используйте определения IV, UV, IVSIZE, I32SIZE и так далее. Избегайте таких вещей, как I32, потому что они не гарантируют точное значение 32 бита, они по крайней мере 32 бита, а также не гарантируется, что они будут int или long. Если вам явно нужны 64-битные переменные, используйте I64 и U64.

  • Предположение, что можно обращаться к любому типу указателя для любого типа данных

    char *p = ...;
    long pony = *(long *)p;    /* BAD */

    Многие платформы, вполне обоснованно, выдадут дамп ядра вместо пони, если p не выровнен правильно.

  • Приведение lvalue

    (int)*p = ...;    /* BAD */

    Просто непереносимо. Добавьте lvalue нужного типа или, возможно, используйте временные переменные или грязные трюки с объединениями.

  • Предположение чего угодно о структурах (особенно тех, которыми вы не управляете, например, из системных заголовков)

    • Что определенное поле существует в структуре

    • Что не существует других полей, кроме тех, которые вам известны

    • Что поле имеет определённую знаковость, размер или тип

    • Что поля расположены в определённом порядке

      • Хотя C гарантирует порядок, определённый в определении структуры, между различными платформами определения могут отличаться

    • Что sizeof(struct) или выравнивание одинаковы во всех местах

      • Между полями могут быть байты заполнения для выравнивания полей — байты могут быть любыми

      • Структуры должны быть выровнены по максимальному выравниванию, необходимому для полей — для встроенных типов это обычно эквивалентно sizeof() поля

  • Предположение, что кодовая страница — ASCII

    Perl может компилироваться и выполняться на платформах EBCDIC. См. perlebcdic. В основном это прозрачно, но из-за различий в кодовых страницах вы не должны использовать числовые (десятичные, восьмеричные или шестнадцатеричные) константы для указания символов. Вы можете безопасно сказать 'A', но не 0x41. Вы можете безопасно сказать '\n', но не \012. Однако вы можете использовать макросы, определённые в utf8.h, для переносимого указания любого кодового пункта. LATIN1_TO_NATIVE(0xDF) будет кодовым пунктом, обозначающим строчную букву с острым на любом используемой платформе (на платформах ASCII он компилируется без добавления дополнительного кода, поэтому нет никакой потери производительности на таких платформах). Допустимые входные данные для LATIN1_TO_NATIVE находятся в диапазоне от 0x00 до 0xFF. Если ваш вход не гарантированно находится в этом диапазоне, используйте UNICODE_TO_NATIVE вместо этого. NATIVE_TO_LATIN1 и NATIVE_TO_UNICODE переводят в обратном направлении.

    Если вам нужна строковая форма символа, который не имеет мнемонического названия в C, вы должны добавить его в список в regen/unicode_constants.pl, чтобы Perl создавал #define для вас на основе текущей платформы.

    Обратите внимание, что макросы isFOO и toFOO в handy.h корректно работают с кодовыми точками и строками нативных платформ.

    Также обратите внимание, что диапазон 'A' - 'Z' в ASCII — это непрерывная последовательность из 26 заглавных букв. Это неверно для EBCDIC. Также неверно для 'a' - 'z'. Но '0' - '9' — это непрерывный диапазон в обеих системах. Не делайте предположений о других диапазонах. (Обратите внимание, что специальная обработка диапазонов в шаблонах регулярных выражений и транслитерациях создаёт видимость, что вышеупомянутые диапазоны являются непрерывными.)

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

    UTF-8 и UTF-EBCDIC — это два разных кодирования, используемых для представления кодовых точек Юникода как последовательностей байтов. Макросы с одинаковыми именами (но разными определениями) в utf8.h и utfebcdic.h используются для того, чтобы вызывающий код думал, что существует только одно такое кодирование. Это почти всегда обозначается как utf8, но это также означает EBCDIC-версию. Снова, комментарии в коде могут быть неверными, даже если сам код верен. Например, понятие UTF-8 invariant characters отличается между ASCII и EBCDIC. На платформах ASCII инвариантны только символы, у которых не установлен старший бит (т. е. чьи порядковые номера являются строгими ASCII, 0—127), и документация и комментарии в коде могут это предполагать, часто ссылаться на что-то вроде, скажем, hibit. Ситуация отличается и не так проста на машинах EBCDIC, но если сам код правильно использует макрос NATIVE_IS_INVARIANT(), он будет работать, даже если комментарии неверны.

    Как отмечено в "TESTING" в perlhack, файл t/charset_tools.pl содержит некоторые полезные функции для написания тестов, которые работают как на платформах ASCII, так и EBCDIC. Иногда, однако, тест не может использовать функцию, и неудобно иметь разные версии тестов в зависимости от платформы. Есть 20 кодовых точек, которые одинаковы во всех 4 кодовых страницах, которые в настоящее время распознаются Perl (3 кодовые страницы EBCDIC плюс ISO 8859-1 (ASCII/Latin1)). Их можно использовать в таких тестах, хотя существует небольшая вероятность, что Perl станет доступным в ещё одной кодовой странице, сломав ваш тест. Все, кроме одной, из этих кодовых точек — управляющие символы C0. Наиболее значимые управляющие символы, которые одинаковы, — \0, \r, и \N{VT} (также задаваемые как \cK, \x0B, \N{U+0B}, или \013). Единственный не управляющий символ — U+00B6 PILCROW SIGN. Управляющие символы, которые одинаковы, имеют одинаковую двоичную запись во всех 4 кодовых страницах, независимо от того, является ли строка, содержащая их, UTF-8. Двоичная запись для U+B6 одинакова во всех 4 кодовых страницах для строк, не являющихся UTF-8, но отличается в каждой из них, когда содержащая её строка закодирована в UTF-8. Единственные другие кодовые точки, у которых есть какое-либо сходство во всех 4 кодовых страницах, — это пара 0xDC и 0xFC. Вместе они представляют заглавную и строчную буквы LATIN LETTER U WITH DIAERESIS, но какая заглавная, а какая строчная, может быть перевёрнута: 0xDC — заглавная в Latin1, а 0xFC — строчная, в то время как 0xFC — заглавная в EBCDIC, а 0xDC — строчная. Этот факт можно использовать при написании регистронезависимых тестов, которые одинаковы для всех 4 кодовых страниц.

  • Предположение, что кодовая страница — просто ASCII

    ASCII — это 7-битовое кодирование, но байты содержат 8 бит. 128 дополнительных символов имеют разные значения в зависимости от локального набора. При отсутствии локального набора эти дополнительные символы обычно считаются неназначенными, что создаёт некоторые проблемы. Эта проблема исправлена начиная с версии 5.12, так что эти символы могут рассматриваться как Latin-1 (ISO-8859-1).

  • Смешивание #define и #ifdef

    #define BURGLE(x) ... \
    #ifdef BURGLE_OLD_STYLE        /* BAD */
    ... do it the old way ... \
    #else
    ... do it the new way ... \
    #endif

    Нельзя переносимо «складывать» директивы cpp. Например, в вышеприведённом случае вам нужны два отдельных #define BURGLE(), по одному для каждой ветви #ifdef.

  • Добавление некомментируемого содержимого после #endif или #else

    #ifdef SNOSH
    ...
    #else !SNOSH    /* BAD */
    ...
    #endif SNOSH    /* BAD */

    После #endif и #else нельзя переносимо добавлять что-либо, кроме комментариев. Если вы хотите задокументировать выполняемые действия (что очень полезно, особенно если ветви длинные), используйте комментарии C:

    #ifdef SNOSH
    ...
    #else /* !SNOSH */
    ...
    #endif /* SNOSH */

    Опция gcc -Wendif-labels предупреждает о плохом варианте (по умолчанию начиная с Perl 5.9.4).

  • Наличие запятой после последнего элемента списка перечисления

    enum color {
      CERULEAN,
      CHARTREUSE,
      CINNABAR,     /* BAD */
    };

    не переносимо. Удалите последнюю запятую.

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

  • Смешивание указателей на signed char с указателями на unsigned char

    int foo(char *s) { ... }
    ...
    unsigned char *t = ...; /* Or U8* t = ... */
    foo(t);   /* BAD */

    Хотя это и допустимая практика, это, безусловно, сомнительно и совершенно фатально по крайней мере на одной платформе: например, VMS cc рассматривает это как фатальную ошибку. Одна из причин, по которой люди часто совершают эту ошибку, заключается в том, что у «голого char» и, следовательно, у «голого указателя на char» неопределённый знак: результат зависит от компилятора и флагов компилятора и базовой платформы, является ли результат знаковым или беззнаковым. По той же причине использование 'char' в качестве индекса массива — плохая практика.

  • Макросы, имеющие строковые константы и их аргументы как подстроки строковых констант

    #define FOO(n) printf("number = %d\n", n)    /* BAD */
    FOO(10);

    Семантика до ANSI для этого была эквивалентна

    printf("10umber = %d\10");

    что, вероятно, не то, что вы ожидали. К сожалению, по крайней мере один достаточно распространённый и современный компилятор C выполняет «реальную обратную совместимость» здесь. В AIX это по-прежнему происходит, даже если остальная часть компилятора AIX очень счастливо работает с C89.

  • Использование форматов printf для не основных типов C

    IV i = ...;
    printf("i = %d\n", i);    /* BAD */

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

    Ещё хуже ситуация с более специфическими типами (определёнными шагом конфигурации Perl в config.h):

    Uid_t who = ...;
    printf("who = %d\n", who);    /* BAD */

    Проблема здесь в том, что Uid_t может быть не только не int-широким, но и беззнаковым, в этом случае большие Uid будут выводиться как отрицательные значения.

    Простого решения нет из-за ограниченной интеллектуальности printf(), но для многих типов доступен правильный формат с суффиксом 'f' или '_f', например:

    IVdf /* IV in decimal */
    UVxf /* UV is hexadecimal */
    
    printf("i = %"IVdf"\n", i); /* The IVdf is a string constant. */
    
    Uid_t_f /* Uid_t in decimal */
    
    printf("who = %"Uid_t_f"\n", who);

    Или вы можете попробовать привести к типу «достаточно широкому»:

    printf("i = %"IVdf"\n", (IV)something_very_small_and_signed);

    См. "Форматированный вывод Size_t и SSize_t" в perlguts, чтобы узнать, как вывести эти значения.

    Также помните, что формат %p действительно требует указателя на void:

    U8* p = ...;
    printf("p = %p\n", (void*)p);

    Опция gcc -Wformat обнаруживает такие проблемы.

  • Слепое передача va_list

    Не все платформы поддерживают передачу va_list в дальнейшие функции varargs (stdarg). Правильным способом является копирование va_list с помощью Perl_va_copy(), если определено NEED_VA_COPY.

  • Использование выражений-высказываний gcc

    val = ({...;...;...});    /* BAD */

    Хотя это и приятное расширение, оно не переносимо. Исторически, Perl использовал их в макросах, если они были доступны, чтобы получить некоторую дополнительную скорость (в основном как необычную форму инлайнинга), но теперь мы поддерживаем (или эмулируем) функции C99 static inline, поэтому используйте их вместо этого. Объявляйте функции как PERL_STATIC_INLINE, чтобы прозрачно переключаться на эмуляцию при необходимости.

  • Связывание нескольких операторов в макросе

    Используйте макросы STMT_START и STMT_END.

    STMT_START {
       ...
    } STMT_END

    Но с ними могут быть тонкие (но избежимые, если вы сделаете всё правильно) ошибки; см. "STMT_START" в perlapi для рекомендаций по их использованию.

  • Проверка операционных систем или версий, когда нужно проверять функциональность

    #ifdef __FOONIX__    /* BAD */
    foo = quux();
    #endif

    Если вы не уверены на 100%, что quux() доступен только для операционной системы «Foonix» и что он доступен и корректно работает для всех прошлых, настоящих и будущих версий «Foonix», вышеприведённый код очень неправильный. Это более корректный подход (хотя всё ещё не идеальный, поскольку ниже приведён проверка на этапе компиляции):

    #ifdef HAS_QUUX
    foo = quux();
    #endif

    Как HAS_QUUX определяется там, где это необходимо? Ну, если Foonix достаточно униксианская, чтобы иметь возможность запускать скрипт Configure, и Configure умеет определять и тестировать quux(), HAS_QUUX будет определён корректно. В других платформах, соответствующий этап конфигурации, будем надеяться, сделает то же самое.

    В крайнем случае, если вы не можете ждать, пока Configure будет обучен, или если вы имеете обоснованное предположение о том, где quux() может быть доступен, вы можете временно попробовать следующее:

    #if (defined(__FOONIX__) || defined(__BARNIX__))
    # define HAS_QUUX
    #endif
    
    ...
    
    #ifdef HAS_QUUX
    foo = quux();
    #endif

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

    Хорошим ресурсом по предопределённым макросам для различных операционных систем, компиляторов и так далее является http://sourceforge.net/p/predef/wiki/Home/

  • Предполагая, что содержимое статической памяти, на которую указывают значения, возвращаемые Perl-обёртками для функций C-библиотеки, не меняется. Многие функции C-библиотеки возвращают указатели на статическое хранилище, которое может быть перезаписано последующими вызовами тех же или связанных функций. Perl имеет обёртки для некоторых из этих функций. Изначально многие из этих обёрток возвращали эти изменчивые указатели. Но со временем почти все они эволюционировали до возврата стабильных копий. Чтобы справиться с оставшимися, выполните "savepv" в perlapi, чтобы создать копию, тем самым избежав этих проблем. Вам придётся освободить копию, когда вы закончите, чтобы избежать утечек памяти. Если вы не контролируете, когда это произойдёт, вам необходимо создать копию в скаляре mortal, примерно так

    SvPVX(sv_2mortal(newSVpv(volatile_string, 0)))

Проблемные системные интерфейсы

  • Строки Perl НЕ такие же, как строки C: они могут содержать NUL символы, в то время как строка C завершается первым NUL. Вот почему функции Perl API, которые работают со строками, обычно принимают указатель на первый байт и либо длину, либо указатель на байт сразу за последним.

    И это причина, по которой многие функции обработки строк C-библиотеки не должны использоваться. Они не учитывают полную общность строк Perl. Возможно, ваши тестовые примеры не содержат вложенных NUL символов, и поэтому тесты проходят, в то время как в реальных сценариях, где-то в будущем, они могут и не пройти. Здесь урок: включайте NUL символы в свои тесты. Сейчас в большинстве реальных случаев редко встречаются NUL символы, поэтому ваш код может работать, пока однажды не встретится NUL.

    Вот пример. Десятилетиями в ядре perl было принято использовать strchr("list", c) для проверки, является ли символ c любым из символов в "list", строке в двойных кавычках, содержащей набор символов, среди которых мы ищем c . Пока c не NUL, всё работает. Но когда c является NUL, strchr возвращает указатель на завершающий NUL в "list". Это вероятно приведёт к сегфолту или проблеме безопасности, когда вызывающий код использует этот конечный указатель в качестве начального для чтения.

    Решение этой и многих подобных проблем заключается в использовании функций C-библиотеки mem-foo вместо них. В этом случае memchr можно использовать для проверки, находится ли c в "list", и это работает, даже если c является NUL. Эти функции нуждаются в дополнительном параметре для указания длины строки. В случае буквальных строковых параметров perl определил макросы, которые вычисляют длину за вас. См. "Обработка строк" в perlapi.

  • malloc(0), realloc(0), calloc(0, 0) не являются переносимыми. Для переносимости выделяйте как минимум один байт. (В целом, вам редко нужно работать на этом низком уровне, вместо этого используйте различные обёртки malloc.)

  • snprintf() - тип возвращаемого значения не переносимый. Используйте my_snprintf() вместо.

Проблемы безопасности

Наконец, здесь приведены различные советы для безопасного программирования. См. также perlclib для замены libc/stdio, которые следует использовать.

  • Не используйте gets()

    Или мы вас публично осмеём. Серьёзно.

  • Не используйте tmpfile()

    Используйте mkstemp() вместо.

  • Не используйте strcpy() или strcat() или strncpy() или strncat()

    Используйте my_strlcpy() и my_strlcat() вместо: они либо используют встроенную реализацию, либо собственную реализацию Perl (заимствованную из общедоступной реализации INN).

  • Не используйте sprintf() или vsprintf()

    Если вы действительно хотите только обычные байтовые строки, используйте my_snprintf() и my_vsnprintf() вместо, которые будут пытаться использовать snprintf() и vsnprintf(), если эти более безопасные API доступны. Если вы хотите что-то более сложное, чем просто байтовую строку, используйте Perl_form() или SVs и Perl_sv_catpvf().

    Обратите внимание, что в glibc printf(), sprintf(), и т. д. были ошибки до версии glibc 2.17. Они не позволят использовать формат %.s с точностью, чтобы создать строку, которая не является допустимой UTF-8, если текущий базовый локаль программы — UTF-8. Происходит то, что формат %s и его операнд просто пропускаются без уведомления. https://sourceware.org/bugzilla/show_bug.cgi?id=6530.

  • Не используйте atoi()

    Используйте grok_atoUV() вместо. atoi() имеет не определённое поведение при переполнении и не может использоваться для пошагового анализа. Также он зависит от локали, что плохо.

  • Не используйте strtol() или strtoul()

    Используйте grok_atoUV() вместо. strtol() или strtoul() (или их аналогичные макросы для IV/UV, Strtol() и Strtoul(), или Atol() и Atoul()) зависят от локали, что плохо.

ОТЛАДКА

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

Работа с Perl

Чтобы по-настоящему покопаться в Perl, вам, вероятно, захочется скомпилировать Perl для отладки, примерно так:

./Configure -d -DDEBUGGING
make

-DDEBUGGING включает флаг -g компилятора C для генерации отладочной информации, что позволит нам пошагово выполнять программу и видеть, в какой C-функции мы находимся (без отладочной информации мы можем видеть только числовые адреса функций, что не очень полезно). Также будет включён символ компиляции DEBUGGING, который включает весь внутренний отладочный код в Perl. Есть много вещей, которые вы можете отладить с этим: perlrun перечисляет их все, и лучший способ узнать о них — поиграться с ними. Вероятно, самые полезные параметры —

l  Context (loop) stack processing
s  Stack snapshots (with v, displays all stacks)
t  Trace execution
o  Method and overloading resolution
c  String/numeric conversions

Например

$ perl -Dst -e '$a + 1'
....
(-e:1)	gvsv(main::a)
    =>  UNDEF
(-e:1)	const(IV(1))
    =>  UNDEF  IV(1)
(-e:1)	add
    =>  NV(1)

Некоторые из функций отладочного кода можно реализовать с помощью обычного perl с использованием модулей XS:

-Dr => use re 'debug'
-Dx => use O 'Debug'

Использование отладчика уровня исходного кода

Если вывод отладки -D вам не поможет, пришло время выполнить пошаговое выполнение кода perl с помощью отладчика уровня исходного кода.

  • Мы будем использовать gdb в наших примерах; принципы будут применяться к любому отладчику (многие поставщики называют свой отладчик dbx), но проверьте руководство того, который вы используете.

Чтобы запустить отладчик, введите

gdb ./perl

Или, если у вас есть дамп ядра:

gdb ./perl core

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

(gdb)

help даст вам доступ к документации, но вот самые полезные команды:

  • run [args]

    Запустить программу с заданными аргументами.

  • break function_name

  • break source.c:xxx

    Сообщает отладчику, что мы хотим приостановить выполнение, когда мы достигнем либо указанной функции (но см. "Внутренние функции" в perlguts!), либо указанной строки в указанном файле исходного кода.

  • step

    Выполнить программу по одной строке за раз.

  • next

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

  • continue

    Выполнить до следующей точки останова.

  • finish

    Выполнить до конца текущей функции, а затем остановиться.

  • 'enter'

    Просто нажатие Enter повторит последнее действие — это благодать, когда продвигаешься по километрам исходного кода.

  • ptype

    Выводит C-определение аргумента.

    (gdb) ptype PL_op
    type = struct op {
        OP *op_next;
        OP *op_sibparent;
        OP *(*op_ppaddr)(void);
        PADOFFSET op_targ;
        unsigned int op_type : 9;
        unsigned int op_opt : 1;
        unsigned int op_slabbed : 1;
        unsigned int op_savefree : 1;
        unsigned int op_static : 1;
        unsigned int op_folded : 1;
        unsigned int op_spare : 2;
        U8 op_flags;
        U8 op_private;
    } *
  • print

    Выполнить данный C-код и вывести результат. ПРЕДУПРЕЖДЕНИЕ: Perl активно использует макросы, и gdb не обязательно поддерживает макросы (см. ниже "поддержка макросов gdb"). Вам нужно будет заменить их самим или вызвать cpp на файлах исходного кода (см. "Цели .i"). Например, вы не можете сказать

    print SvPV_nolen(sv)

    но вы должны сказать

    print Perl_sv_2pv_nolen(sv)

Для вас может быть полезно иметь «словарь макросов», который вы можете создать, сказав cpp -dM perl.c | sort. Даже тогда cpp не будет рекурсивно применять эти макросы за вас.

Поддержка макросов gdb

Недавние версии gdb имеют довольно хорошую поддержку макросов, но для её использования вам нужно скомпилировать Perl с включением определений макросов в отладочную информацию. Используя gcc версии 3.1, это означает конфигурацию с -Doptimize=-g3. Другие компиляторы могут использовать другой переключатель (если они вообще поддерживают отладочные макросы).

Выгрузка структур данных Perl

Один из способов обойти этот макрос-ад может быть использование функций выгрузки в dump.c; они работают немного как внутренний Devel::Peek, но они также охватывают операторы и другие структуры, к которым у вас нет доступа из Perl. Возьмём пример. Мы воспользуемся $a = $b + $c что мы использовали раньше, но дадим ему немного контекста: $b = "6XXXX"; $c = 2.3;. Где хорошее место для остановки и исследования?

Что насчёт pp_add, функции, которую мы рассматривали ранее для реализации оператора %%%CODE_BLOCK_242%%?:

(gdb) break Perl_pp_add
Breakpoint 1 at 0x46249f: file pp_hot.c, line 309.

Обратите внимание, что мы используем Perl_pp_add, а не pp_add, — см. "Внутренние функции" в perlguts. После установки точки останова мы можем запустить нашу программу:

(gdb) run -e '$b = "6XXXX"; $c = 2.3; $a = $b + $c'

Много мусора пройдёт, пока gdb считывает соответствующие исходные файлы и библиотеки, и затем:

Breakpoint 1, Perl_pp_add () at pp_hot.c:309
1396    dSP; dATARGET; bool useleft; SV *svl, *svr;
(gdb) step
311           dPOPTOPnnrl_ul;
(gdb)

Мы уже рассматривали этот фрагмент кода и выяснили, что dPOPTOPnnrl_ul организует размещение двух NV в left и right — давайте немного расширим его:

#define dPOPTOPnnrl_ul  NV right = POPn; \
                        SV *leftsv = TOPs; \
                        NV left = USE_LEFT(leftsv) ? SvNV(leftsv) : 0.0

POPn берёт SV из вершины стека и получает его NV либо напрямую (если SvNOK установлено), либо вызывая функцию sv_2nv. TOPs берёт следующий SV из вершины стека — да, POPn использует TOPs, но не удаляет его. Затем мы используем SvNV, чтобы получить NV из leftsv таким же образом, как и раньше — да, POPn использует SvNV.

Поскольку у $b нет NV, нам придётся использовать sv_2nv, чтобы его преобразовать. Если мы ещё раз выполним шаг, мы окажемся там:

(gdb) step
Perl_sv_2nv (sv=0xa0675d0) at sv.c:1669
1669        if (!sv)
(gdb)

Теперь мы можем использовать Perl_sv_dump, чтобы исследовать SV:

(gdb) print Perl_sv_dump(sv)
SV = PV(0xa057cc0) at 0xa0675d0
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0xa06a510 "6XXXX"\0
CUR = 5
LEN = 6
$1 = void

Мы знаем, что получим 6, поэтому закончим подпрограмму:

(gdb) finish
Run till exit from #0  Perl_sv_2nv (sv=0xa0675d0) at sv.c:1671
0x462669 in Perl_pp_add () at pp_hot.c:311
311           dPOPTOPnnrl_ul;

Также можно вывести этот оператор: текущий оператор всегда хранится в PL_op, и мы можем вывести его с помощью Perl_op_dump. Это даст результат, похожий на вывод модуля CPAN B::Debug.

(gdb) print Perl_op_dump(PL_op)
{
13  TYPE = add  ===> 14
    TARG = 1
    FLAGS = (SCALAR,KIDS)
    {
        TYPE = null  ===> (12)
          (was rv2sv)
        FLAGS = (SCALAR,KIDS)
        {
11          TYPE = gvsv  ===> 12
            FLAGS = (SCALAR)
            GV = main::b
        }
    }

# завершить это позже #

Использование gdb для просмотра определённых частей программы

В примере выше вы знали, что искать Perl_pp_add, но что если были многочисленные вызовы, и вы не знали, какой оператор ищете?

Один из способов — вставить редкий вызов где-то рядом с тем, что вы ищете. Например, можно добавить study перед вашим методом:

study;

А в gdb выполните:

(gdb) break Perl_pp_study

Затем переходите к шагам, пока не достигнете того, что ищете. Это хорошо работает в цикле, если нужно прерываться только на определённых итерациях:

for my $c (1..100) {
    study if $c == 50;
}

Использование gdb для просмотра действий парсера/лексера

Если вы хотите увидеть, что делает Perl при парсинге/лексировании вашего кода, вы можете использовать BEGIN {}:

print "Before\n";
BEGIN { study; }
print "After\n";

И в gdb:

(gdb) break Perl_pp_study

Если вы хотите увидеть, что делает парсер/лексер внутри блоков if и им подобных, вам нужно приложить больше усилий:

if ($a && $b && do { BEGIN { study } 1 } && $c) { ... }

СТАТИЧЕСКИЙ АНАЛИЗ ИСТОЧНИКА КОДА

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

lint

Хороший старый инспектор качества кода C, lint, доступен на нескольких платформах, но имейте в виду, что существуют различные реализации разных поставщиков, что означает, что флаги не идентичны на разных платформах.

Есть цель lint в Makefile, но вам может потребоваться поиграть с флагами (см. выше).

Coverity

Coverity (http://www.coverity.com/) — продукт, похожий на lint, и, используя их продукт в качестве тестовой площадки, они периодически проверяют несколько открытых проектов и предоставляют учётные записи разработчикам открытых проектов для баз данных ошибок.

Есть настройка Coverity для проекта perl5: https://scan.coverity.com/projects/perl5

HP-UX cadvise (Code Advisor)

HP предлагает продукт статического анализа C/C++ для HP-UX под названием Code Advisor. (Ссылка здесь не приведена, потому что URL ужасно длинный и кажется ужасно нестабильным; используйте поисковую систему по своему выбору для поиска.) Рекомендуется использовать рецепт cadvise_cc с Configure ... -Dcc=./cadvise_cc (см. руководство пользователя cadvise); а также использовать +wall.

cpd (детектор копирования и вставки)

Инструмент cpd обнаруживает код, скопированный и вставленный. Если одна копия скопированного и вставленного кода изменится, все остальные места, вероятно, также должны быть изменены. Поэтому такой код, вероятно, должен быть преобразован в подпрограмму или макрос.

cpd (https://pmd.github.io/latest/pmd_userdocs_cpd.html) — часть проекта pmd (https://pmd.github.io/). pmd изначально был написан для статического анализа кода Java, но позже часть cpd была расширена для обработки также C и C++.

Загрузите pmd-bin-X.Y.zip () с сайта SourceForge, извлеките pmd-X.Y.jar из него, а затем запустите его на исходном коде следующим образом:

java -cp pmd-X.Y.jar net.sourceforge.pmd.cpd.CPD \
 --minimum-tokens 100 --files /some/where/src --language c > cpd.txt

В случае ограничений памяти используйте параметр -Xmx:

java -Xmx512M ...

Предупреждения gcc

Хотя можно много написать о проблемах непротиворечивости и охвата предупреждений gcc (например, -Wall не означает «все предупреждения», или некоторые общие проблемы с переносимостью не покрываются -Wall, или -ansi и -pedantic — это плохо определённый набор предупреждений и так далее), gcc по-прежнему полезен для поддержания чистоты кода.

-Wall по умолчанию включён.

Было бы неплохо, чтобы -pedantic) всегда был включён, но, к сожалению, он не безопасен на всех платформах — например, смертельные конфликты с системными заголовками (Solaris — яркий пример). Если используется Configure -Dgccansipedantic, фронтенд cflags выбирает -pedantic для платформ, где он известен как безопасный.

Добавляются следующие дополнительные флаги:

  • -Wendif-labels

  • -Wextra

  • -Wc++-compat

  • -Wwrite-strings

  • -Werror=pointer-arith

  • -Werror=vla

Следующие флаги было бы неплохо иметь, но для начала потребуется свой «стабильный мастер Аугея»:

  • -Wshadow

  • -Wstrict-prototypes

-Wtraditional — ещё один пример неприятной тенденции gcc объединять множество предупреждений в одном переключателе (практически невозможно развернуть, так как он будет много жаловаться), но он включает некоторые полезные предупреждения, которые было бы неплохо иметь отдельно, такие как предупреждение о строковых константах внутри макросов, содержащих аргументы макроса: это поведение отличалось до ANSI и отличается в ANSI, и некоторые компиляторы C до сих пор находятся в процессе перехода, например, AIX.

Предупреждения других компиляторов C

Другие компиляторы C (да, существуют другие компиляторы C, помимо gcc) часто включают в себя режимы «строгий ANSI» или «строгий ANSI с некоторыми расширениями для переносимости», например, Sun Workshop имеет режим -Xa (хотя неявно), или DEC (в наши дни HP…) имеет режим -std1.

ДЕБАГГЕРЫ ПАМЯТИ

ПРИМЕЧАНИЕ 1: Выполнение под старыми отладчиками памяти, такими как Purify, valgrind или Third Degree, значительно замедляет выполнение: секунды превращаются в минуты, минуты в часы. Например, начиная с Perl 5.8.1, ext/Encode/t/Unicode.t чрезвычайно долго выполняется под e.g. Purify, Third Degree и valgrind. Под valgrind он занимает более шести часов, даже на быстром компьютере. Указанный тест, вероятно, делает что-то, что довольно недружелюбно относится к отладчикам памяти. Если вы не хотите ждать, то можете просто убить процесс Perl.

ПРИМЕЧАНИЕ 2: Чтобы свести к минимуму ложные срабатывания при обнаружении утечек памяти (см. "PERL_DESTRUCT_LEVEL" для получения дополнительной информации), необходимо установить переменную среды PERL_DESTRUCT_LEVEL в значение 2. Например, так:

env PERL_DESTRUCT_LEVEL=2 valgrind ./perl -Ilib ...

ПРИМЕЧАНИЕ 3: Известны утечки памяти при наличии ошибок компиляции в eval или require, наличие S_doeval в стеке вызовов — хороший признак этих ошибок. К сожалению, устранение этих утечек нетривиально, но их необходимо устранить в конечном итоге.

ПРИМЕЧАНИЕ 4: DynaLoader не очистит себя полностью, если Perl не скомпилирован с опцией Configure -Accflags=-DDL_UNLOAD_ALL_AT_EXIT.

valgrind

Инструмент valgrind можно использовать для поиска утечек памяти и нелегальных обращений к куче. По состоянию на версию 3.3.0 Valgrind поддерживает только Linux на x86, x86-64 и PowerPC, а также Darwin (OS X) на x86 и x86-64. Специальная цель «test.valgrind» может использоваться для запуска тестов под управлением valgrind. Обнаруженные ошибки и утечки памяти записываются в файлы с именем testfile.valgrind, а по умолчанию вывод отображается встроеным.

Пример использования:

make test.valgrind

Поскольку valgrind добавляет значительную нагрузку, тесты будут выполняться гораздо дольше. Для ускорения тесты valgrind поддерживают параллельное выполнение:

TEST_JOBS=9 make test.valgrind

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

VG_OPTS='-q --leak-check=no --show-reachable=no' TEST_JOBS=9 \
    make test.valgrind

Valgrind также предоставляет инструмент cachegrind, вызываемый для Perl так:

VG_OPTS=--tool=cachegrind make test.valgrind

Поскольку системные библиотеки (в частности, glibc) также вызывают ошибки, valgrind позволяет подавлять такие ошибки с помощью файлов подавления. Входящий в комплект поставки valgrind файл подавления по умолчанию уже обрабатывает множество из них. Некоторые дополнительные подавления определены в t/perl.supp.

Для получения valgrind и дополнительной информации см.

http://valgrind.org/

AddressSanitizer

AddressSanitizer («ASan») состоит из модуля инструментирования компилятора и библиотеки времени выполнения malloc. ASan доступен для различных архитектур, операционных систем и компиляторов (см. ссылку на проект ниже). Он проверяет небезопасное использование памяти, такое как использование после освобождения и переполнение буфера, и достаточно быстрый, чтобы вы могли легко скомпилировать отладочный или оптимизированный Perl с ним. Современные версии ASan проверяют утечки памяти по умолчанию на большинстве платформ, в противном случае (например, x86_64 OS X) эту функцию можно включить с помощью ASAN_OPTIONS=detect_leaks=1.

Для компиляции Perl с AddressSanitizer, ваше обращение к Configure должно выглядеть так:

sh Configure -des -Dcc=clang \
   -Accflags=-fsanitize=address -Aldflags=-fsanitize=address \
   -Alddlflags=-shared\ -fsanitize=address \
   -fsanitize-blacklist=`pwd`/asan_ignore

где эти аргументы означают:

  • -Dcc=clang

    Это следует заменить полным путем к вашему исполняемому файлу clang, если он не находится в вашей переменной среды PATH.

  • -Accflags=-fsanitize=address

    Компилировать исходные коды Perl и расширений с AddressSanitizer.

  • -Aldflags=-fsanitize=address

    Скомпоновать исполняемый файл Perl с AddressSanitizer.

  • -Alddlflags=-shared\ -fsanitize=address

    Компоновать динамические расширения с AddressSanitizer. Вы должны вручную указать -shared, потому что использование -Alddlflags=-shared предотвратит настройку Configure по умолчанию для lddlflags, который обычно содержит -shared (по крайней мере, в Linux).

  • -fsanitize-blacklist=`pwd`/asan_ignore

    AddressSanitizer проигнорирует функции, перечисленные в файле asan_ignore. (В этом файле должно содержаться краткое объяснение причины перечисления каждой функции).

См. также https://github.com/google/sanitizers/wiki/AddressSanitizer.

ПРОФИЛИРОВАНИЕ

В зависимости от вашей платформы существует множество способов профилирования Perl.

Существуют два распространенных метода профилирования исполняемых файлов: статистическое временное выборки и подсчёт базовых блоков.

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

Второй метод разбивает сгенерированный код на базовые блоки. Базовые блоки — это части кода, в которые входят только в начале и выходят только в конце. Например, условный переход запускает базовый блок. Профилирование по базовым блокам обычно работает путём инструментирования кода путём добавления в сгенерированный код записи вход в базовый блок #nnnn. Во время выполнения кода счётчики базовых блоков затем обновляются соответствующим образом. Ограничение заключается в том, что добавленный дополнительный код может исказить результаты: опять же, инструменты профилирования обычно пытаются исключить собственное влияние из результатов.

Профилирование с помощью gprof

gprof — это инструмент профилирования, доступный во многих Unix-платформах, который использует статистическую временную выборку. Вы можете создать профилируемую версию perl, скомпилировав её с помощью gcc со флагом -pg. Измените config.sh или перезапустите Configure. Запуск профилируемой версии Perl создаст выходной файл под названием gmon.out, который содержит данные профилирования, собранные во время выполнения.

Быстрый совет:

$ sh Configure -des -Dusedevel -Accflags='-pg' \
    -Aldflags='-pg' -Alddlflags='-pg -shared' \
    && make perl
$ ./perl ... # creates gmon.out in current directory
$ gprof ./perl > out
$ less out

(вероятно, вам нужно добавить -shared к строке <-Alddlflags> до решения проблемы RT #118199)

Инструмент gprof может отображать собранные данные различными способами. Обычно gprof понимает следующие параметры:

  • -a

    Исключить статически определённые функции из профиля.

  • -b

    Исключить подробные описания в профиле.

  • -e routine

    Исключить заданную процедуру и её потомков из профиля.

  • -f routine

    Отобразить только заданную процедуру и её потомков в профиле.

  • -s

    Сгенерировать сводный файл под названием gmon.sum, который затем можно передать последующим запускам gprof для накопления данных по нескольким запускам.

  • -z

    Отобразить процедуры, у которых нулевое использование.

Для более подробного объяснения доступных команд и форматов вывода см. локальную документацию gprof.

Профилирование с помощью GCC gcov

Профилирование по базовым блокам официально доступно в gcc 3.0 и более поздних версиях. Вы можете создать профилируемую версию perl, скомпилировав её с помощью gcc со флагами -fprofile-arcs -ftest-coverage. Измените config.sh или перезапустите Configure.

Быстрый совет:

$ sh Configure -des -Dusedevel -Doptimize='-g' \
    -Accflags='-fprofile-arcs -ftest-coverage' \
    -Aldflags='-fprofile-arcs -ftest-coverage' \
    -Alddlflags='-fprofile-arcs -ftest-coverage -shared' \
    && make perl
$ rm -f regexec.c.gcov regexec.gcda
$ ./perl ...
$ gcov regexec.c
$ less regexec.c.gcov

(вероятно, вам нужно добавить -shared к строке <-Alddlflags> до решения проблемы RT #118199)

Запуск профилируемой версии Perl вызовет генерацию выходных данных профиля. Для каждого исходного файла будет создан сопровождающий файл .gcda.

Для отображения результатов используется утилита gcov (которая должна быть установлена, если у вас установлен gcc 3.0 или новее). gcov запускается на исходных файлах, например, так

gcov sv.c

что приведёт к созданию файла sv.c.gcov. Файлы .gcov содержат исходный код, снабжённый аннотациями с относительными частотами выполнения, указанными маркерами «#». Если вы хотите сгенерировать файлы .gcov для всех профилируемых объектных файлов, вы можете запустить что-то вроде этого:

for file in `find . -name \*.gcno`
do sh -c "cd `dirname $file` && gcov `basename $file .gcno`"
done

Полезные параметры gcov включают -b, который подведёт итог покрытия базовых блоков, ветвей и вызовов функций, и -c, который вместо относительных частот будет использовать фактические подсчёты. Для получения более подробной информации об использовании gcov и профилировании по базовым блокам с gcc см. последнюю документацию по GNU CC. По состоянию на gcc 4.8, это находится по адресу http://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html#Gcov-Intro

Профилирование с помощью callgrind

callgrind — это инструмент valgrind для профилирования исходного кода. В паре с kcachegrind (интерфейс пользователя на базе Qt) он даёт вам обзор того, где код занимает время, а также возможность изучать вызывающие функции, древовидные структуры вызовов и многое другое. Одним из его преимуществ является то, что вы можете использовать его для Perl и XS-модулей, которые не были скомпилированы со символами отладки.

Если perl скомпилирован с символами отладки (-g), вы можете просмотреть аннотированный исходный код и щелкать по нему, как в HTML-выходе Devel::NYTProf.

Для базового использования:

valgrind --tool=callgrind ./perl ...

По умолчанию он будет записывать выходные данные в callgrind.out.PID, но вы можете изменить это с помощью --callgrind-out-file=...

Для просмотра данных:

kcachegrind callgrind.out.PID

Если вы предпочитаете просматривать данные в терминале, вы можете использовать callgrind_annotate. В его базовой форме:

callgrind_annotate callgrind.out.PID | less

Некоторые полезные параметры:

  • --threshold

    Процентный показатель подсчётов (первичного события сортировки), которые нас интересуют. По умолчанию 99%, 100% могут показать вещи, которые, похоже, отсутствуют.

  • --auto

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

РАЗЛИЧНЫЕ УЛОВКИ

PERL_DESTRUCT_LEVEL

Если вы хотите самостоятельно запустить тесты, например, с помощью valgrind, обратите внимание, что по умолчанию perl не явным образом очищает всю выделенную память (такую, как глобальные области памяти), а вместо этого позволяет функции exit() всей программы «заботиться» о таких выделениях, также известной как «глобальное уничтожение объектов».

Есть способ сказать perl выполнить полную очистку: установите переменную среды PERL_DESTRUCT_LEVEL в ненулевое значение. В t/TEST-оболочке это значение установлено в 2, и это то, что вам нужно сделать тоже, если вы не хотите видеть «глобальные утечки»: например, для запуска под valgrind

env PERL_DESTRUCT_LEVEL=2 valgrind ./perl -Ilib t/foo/bar.t

(Примечание: модуль mod_perl для Apache также использует эту переменную среды для своих собственных целей и расширил её семантику. Для получения дополнительной информации обратитесь к документации mod_perl. Также запущенные потоки делают эквивалент установки этой переменной в значение 1.)

Если в конце выполнения вы получите сообщение N scalars leaked, вы можете перекомпилировать с -DDEBUG_LEAKING_SCALARS, (Configure -Accflags=-DDEBUG_LEAKING_SCALARS), что заставит вывести адреса всех этих утечек SVs вместе с деталями о том, где каждый SV был первоначально выделен. Эта информация также отображается с помощью Devel::Peek. Обратите внимание, что дополнительные детали, записанные с каждым SV, увеличивают использование памяти, поэтому её не следует использовать в производственных средах. Она также преобразует new_SV() из макроса в реальную функцию, так что вы можете использовать свой любимый отладчик, чтобы узнать, где были выделены эти надоедливые SVs.

Если вы обнаружите, что у вас утечка памяти во время выполнения, но ни valgrind, ни -DDEBUG_LEAKING_SCALARS ничего не найдут, вы, вероятно, пропускаете SVs, которые всё ещё доступны и будут должным образом очищены во время уничтожения интерпретатора. В таких случаях использование переключателя -Dm может указать вам источник утечки. Если исполняемый файл был построен с -DDEBUG_LEAKING_SCALARS, -Dm выведет выделения SV помимо выделений памяти. Каждое выделение SV имеет уникальный серийный номер, который будет записан при создании и уничтожении SV. Поэтому, если вы выполняете код утечки в цикле, вам нужно искать SVs, которые создаются, но никогда не уничтожаются между каждым циклом. Если такой SV найден, установите условную точку останова внутри new_SV() и заставьте её останавливаться только тогда, когда PL_sv_serial равно серийному номеру протекающего SV. Тогда вы поймаете интерпретатор ровно в том состоянии, когда SV утечки выделен, что в многих случаях достаточно, чтобы найти источник утечки.

Поскольку -Dm использует слой PerlIO для вывода, он сам по себе выделит довольно много SVs, которые скрыты, чтобы избежать рекурсии. Вы можете обойти слой PerlIO, если используете ведение журнала SV, предоставляемое -DPERL_MEM_LOG вместо этого.

PERL_MEM_LOG

Если скомпилировано с -DPERL_MEM_LOG (-Accflags=-DPERL_MEM_LOG), выделения памяти и SV проходят через функции ведения журнала, что удобно для установки точек останова.

Если -DPERL_MEM_LOG_NOIMPL (-Accflags=-DPERL_MEM_LOG_NOIMPL) также не скомпилировано, функции ведения журнала считывают $ENV{PERL_MEM_LOG}, чтобы определить, нужно ли регистрировать событие, и если да, то как:

$ENV{PERL_MEM_LOG} =~ /m/           Log all memory ops
$ENV{PERL_MEM_LOG} =~ /s/           Log all SV ops
$ENV{PERL_MEM_LOG} =~ /c/           Additionally log C backtrace for
                                    new_SV events
$ENV{PERL_MEM_LOG} =~ /t/           include timestamp in Log
$ENV{PERL_MEM_LOG} =~ /^(\d+)/      write to FD given (default is 2)

Регистрация памяти несколько похожа на -Dm, но независима от -DDEBUGGING, и на более высоком уровне; все использования Newx(), Renew() и Safefree() регистрируются с именем файла и номером строки вызывающего исходного кода (и именем функции C, если поддерживается компилятором C). В отличие от -Dm, которое находится непосредственно в точке malloc(). Регистрация SV аналогична.

Поскольку регистрация не использует PerlIO, все выделения SV регистрируются, и никаких дополнительных выделений SV не вводятся при включении регистрации. Если скомпилировано с -DDEBUG_LEAKING_SCALARS, серийный номер каждого выделения SV также регистрируется.

Вариант c использует функцию Perl_c_backtrace, и поэтому дополнительно требует флага компиляции Configure -Dusecbacktrace для доступа к ней.

DDD над gdb

Тем, кто отлаживает perl с помощью переднего плана DDD над gdb, может быть полезно следующее:

Можно расширить меню ярлыков преобразования данных, так что, например, можно отобразить значение IV SV одним щелчком, не печатая ничего. Для этого просто отредактируйте файл ~/.ddd/init и добавьте после:

! Display shortcuts.
Ddd*gdbDisplayShortcuts: \
/t ()   // Convert to Bin\n\
/d ()   // Convert to Dec\n\
/x ()   // Convert to Hex\n\
/o ()   // Convert to Oct(\n\

следующие две строки:

((XPV*) (())->sv_any )->xpv_pv  // 2pvx\n\
((XPVIV*) (())->sv_any )->xiv_iv // 2ivx

Теперь вы можете выполнять поиск по ivx и pvx, или вы можете подключить туда «преобразование» sv_peek:

Perl_sv_peek(my_perl, (SV*)()) // sv_peek

(my_perl предназначен для многопоточных сборок). Просто помните, что каждая строка, кроме последней, должна заканчиваться \n\

В качестве альтернативы можно редактировать файл init интерактивно через: правая кнопка мыши -> Новый дисплей -> Изменить меню

Примечание: вы можете определить до 20 сокращений преобразований в разделе gdb.

C стек вызовов

На некоторых платформах Perl поддерживает получение стека вызовов уровня C (аналогично тому, что делают символьные отладчики, такие как gdb).

Стек вызовов возвращает трассировку стека кадров вызовов C, с именами символов (имена функций), именами объектов (например, «perl»), и, если это возможно, также местоположениями исходного кода (файл:строка).

Поддерживаемые платформы — Linux и OS X (некоторые *BSD могут работать хотя бы частично, но они ещё не тестировались).

Эта функция не тестировалась с несколькими потоками, но она покажет только стек вызовов потока, выполняющего трассировку.

Функция должна быть включена с помощью Configure -Dusecbacktrace.

-Dusecbacktrace также включает сохранение отладочной информации при компиляции/связывании (часто: -g). Многие компиляторы/линкеры поддерживают как оптимизацию, так и сохранение отладочной информации. Отладочная информация необходима для имён символов и местоположений исходного кода.

Статические функции могут быть невидимыми для стека вызовов.

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

Linux

Вы обязательно должны иметь установленную библиотеку BFD (-lbfd), иначе perl не сможет связаться. BFD обычно распространяется как часть GNU binutils.

Краткое описание: Configure ... -Dusecbacktrace и вам нужен -lbfd.

OS X

Местоположения исходного кода поддерживаются только при наличии установленных Developer Tools. (BFD не нужен.)

Краткое описание: Configure ... -Dusecbacktrace и установка Developer Tools будет хорошим решением.

Необязательно, для тестирования функции, вы можете включить автоматическую выгрузку стека вызовов незадолго до появления сообщения об ошибке или croak (die), добавив -Accflags=-DUSE_C_BACKTRACE_ON_ERROR для Configure.

Если вышеуказанная дополнительная функция не включена, ничего о функции стека вызовов не видно, кроме уровня Perl/XS.

Кроме того, даже если вы включили эту функцию для компиляции, вам нужно включить её во время выполнения с переменной среды: PERL_C_BACKTRACE_ON_ERROR=10. Это должно быть целое число, большее нуля, указывающее желаемое количество кадров.

Получение стека вызовов с уровня Perl (например, с помощью расширения XS) будет гораздо менее интересным, чем можно было бы ожидать: обычно вы увидите runops, entersub, и не многое другое. Этот API предназначен для вызова изнутри реализации Perl, а не из выполнения Perl.

C API для стека вызовов:

get_c_backtrace
free_c_backtrace
get_c_backtrace_dump
dump_c_backtrace

Ядовитые значения

Если вы видите в отладчике область памяти, загадочно заполненную 0xABABABAB или 0xEFEFEFEF, вы, возможно, видите результат работы макросов Poison(), см. perlclib.

Только чтение optree

В среде ithreads optree является только для чтения. Если вы хотите это принудительно, чтобы проверить доступ для записи из ошибочного кода, скомпилируйте с -Accflags=-DPERL_DEBUG_READONLY_OPS, чтобы включить код, который выделяет операционную память через mmap, и устанавливает её только для чтения, когда она прикреплена к подпрограмме. Любой доступ для записи к оператору приводит к SIGBUS и прерыванию.

Этот код предназначен только для разработки и может не быть переносимым даже на все варианты Unix. Кроме того, это 80%-ное решение, так как оно не может сделать все операторы только для чтения. В частности, это не относится к операционным пластам, принадлежащим блокам BEGIN.

Однако, как 80%-ное решение, оно по-прежнему эффективно, так как в прошлом ловило ошибки.

Когда bool — не bool?

На компиляторах до C99 не было, строго говоря, стандартного типа bool, и поэтому были созданы некоторые обходные пути. Макросы TRUE и FALSE по-прежнему доступны как альтернативы true и false. И макрос cBOOL был создан для правильного приведения к истинному/ложному значению во всех случаях, но больше не должен быть необходим. Использование (bool) expr> теперь всегда должно работать.

Нет планов удалять ни TRUE, ни FALSE, ни cBOOL.

Поиск небезопасных усечений

Возможно, вам захочется запустить Configure с чем-то вроде

-Accflags='-Wconversion -Wno-sign-conversion -Wno-shorten-64-to-32'

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

Цели .i

Вы можете развернуть макросы в файле foo.c, сказав

make foo.i

что расширит макросы с помощью cpp. Не пугайтесь результатов.

АВТОР

Этот документ изначально был написан Натаном Торкинтоном и поддерживается почтовой рассылкой perl5-porters.

© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/perlhacktips

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API