Описания линейной системы в статике
можно получить, зная передаточную
функцию системы. Поскольку
структурные схемы в статике можно
получить из структурных схем в динамике,
заменив передаточные функции звеньев
статическими коэффициентами передачи,
найденными по этой формуле.
Правила структурных схем, справедливые
для динамики, можно применить и для
структурных преобразований в статике.
Качество систем автоматического
регулирования в статике определяется
статической ошибкой — разница между
заданным и действительным значениями
регулируемой величины в установившемся
режиме.
Пусть структурная схема САР в статике
имеет вид:
По определению
статическая ошибка =xуст
–yуст.
Найдем Δ через параметры системы
Тогда
где kр·kо
=kраз– статический коэффициент передачи
разомкнутой системы.
Тогда
зависит не только от параметров системы,
но и от входного сигнала.
Поэтому для оценки качества САР применяют
относительную статическую ошибку –
статизм, которую определяют как отношение
абсолютной статический ошибки к заданному
значению регулируемой величины.
Качество
системы в статике тем лучше, чем меньше
статическая ошибка, которая зависит от
величины kраз.
Для уменьшения статической ошибки
нужно:
-
Увеличивать
kраз.
Однако увеличениеkраз
ведёт к уменьшению запаса устойчивости
поэтому увеличиватьkразнужно очень осторожно; -
Включать
в прямую цепь регулирования астатическое
(интегрирующее) звено.
Астатическое звено уменьшает статическую
ошибку системы до 0. Систему с нулевой
статической ошибкой (при отсутствии
остаточного отклонения между заданным
и действительным значениями регулируемой
величины) называется астатической.
Система с наличием статической ошибки
(при наличии остаточного отклонения
между заданным и действительным
значениями регулируемой величины)
называется статической.
Тема 4 Устойчивость систем автоматического регулирования §1. Физическое и математическое определение устойчивости.
Система автоматического регулирования
называется устойчивой, если после снятия
возмущающего воздействия, которое
вывело её из состояния равновесия, она
вновь возвращается в состояние равновесия.
Если система не возвращается в состояние
равновесия после снятия возмущения,
она неустойчива.
устойчивая система (кривые 1, 2)
неустойчивая (3).
Для
определения математического условия
устойчивости САР необходимо решить
дифференциальное уравнение системы,
когда правая часть этого уравнения
равна 0 (при снятии возмущающего
воздействия), и посмотреть, как ведетyвых
(t) при t.
Пусть
Тогда дифференциальное уравнение
системы в операторной форме:
αnpny(p)
+ … +α1py(p)
+αoy(p)
=bmpmx(p)
+ …+ b1px(p)
+ box(p)
Оригинал дифференциального уравнения:
Для определения устойчивости системы,
описываемой этим уравнением, снимем
возмущения x(t)=0 и решим уравнение:
Для этого запишем характеристическое
уравнение:
H(p)
= αnpn
+ …. +α1p+αo= 0.
Как видно из последнего выражения,
характеристическое уравнение звена
или системы – это знаменатель передаточной
функции звена или системы, приравненный
к нулю.
Если p1,p2,
…,pn– корни характеристического уравнения,
то решение этого уравнения имеет вид:
гдеCi– постоянные интегрирования, определяемые
из начальных условий.
Рассмотрим отдельные случаи решения
дифференциального уравнения:
1)p1,p2,
…,pn– отрицательные действительные корни:pi=
-i.
Решение уравнения в этом случае:
.
2) p1,p2,
…,pn— положительные действительные корни:pi=
+i.
–
решение уравнения в этом случае
.
3) p1,p2,
…,pn— корни комплексно-сопряженные с
отрицательной вещественной частью:
pi
= – i
ji
.
4) p1,p2,
…,pn– корни комплексно-сопряженные с
положительной вещественной частью:
pi
= + i
ji
Анализируя все случаи решения
дифференциального уравнения для случая
x(t) = 0, можно сделать вывод:
система
автоматического регулирования устойчива,
если все корни ее характеристического
уравнения отрицательные действительные
или комплексно-сопряженные с отрицательной
действительной частью. Если же среди
корней характеристического уравнения
системы имеется хотя бы один положительный
действительный корень или хотя бы одна
пара комплексно-сопряженных корней с
положительной вещественной частью,
такая система неустойчива.
Математические правила, позволяющие
определить знаки корней алгебраического
(характеристического) уравнения, не
решая это уравнение, в ТАУ называют
критериями устойчивости.
Уменьшение — статическая ошибка
Cтраница 1
Уменьшение статической ошибки осуществляется в результате воздействия астатической части, которая в основном и служит для ликвидации остаточного отклонения. По окончании переходного режима движок реостатного датчика Rn и контактные группы обоих реле приходят в первоначальное среднее положение, соответствующее заданному значению регулируемой величины, а положение рукоятки спаренных сопротивлений Л5 и jR6 ( ручная перестановка) показывает степень открытия регулирующего органа в процентах от полного открытия. Доля участия пропорциональной части в перемещении регулирующего органа зависит от диапазона пропорциональности, а астатической — от величины времени действия астатической части.
[1]
Уменьшение статической ошибки ( а при бесконечно большом коэффициенте усиления усилителя в статике — сведение ее к нулю) в контуре с объектом в виде интегрирующего звена может быть достигнуто путем использования ПИ-регулятора.
[2]
Уменьшение статической ошибки регулирования ( ошибки в установившемся режиме) и увеличение быстродействия системы может быть достигнуто последовательным включением в контур системы автоматического регулирования ( в регулятор, осуществляющий основную обратную связь) безынерционного звена с большим коэффициентом усиления.
[4]
Для уменьшения статической ошибки используют также принцип управления по возмущению в комбинированных САУ. Из множества возмущений выбирается главное, и на основе его измерения воздействуют на объект управления таким образом, чтобы компенсировать естественное влияние данного воздействия на выходную величину системы. Комбинированные системы позволяют значительно снизить статическую ошибку системы без повышения степени ее астатизма.
[5]
Для уменьшения статической ошибки без нарушения устойчивости необходимо в контур ввести первую производную от входного сигнала.
[6]
Для уменьшения статической ошибки следует увеличивать коэффициент усиления системы. До известной степени можно считать, что величина коэффициента усиления определяется требованиями к величине статической ошибки.
[8]
Для уменьшения статической ошибки регулирования иногда рекомендуется установка в приточной камере двух калориферов ( или двух рядов калориферов), соединенных последовательно по воздуху и параллельно по воде. Теплопроизводительность каждого калорифера составляет приблизительно 50 % от общей требуемой. Благодаря этому статическая ошибка выбранного регулятора может быть снижена вдвое, так как регулируется только половина нагрузки. Первый по ходу воздуха калорифер отключается двухпозиционным регулятором, датчик которого установлен на входе наружного воздуха, когда достигается температура, при которой требуется только половина нагрузки — теплопроизводительности. Для определения температуры настройки двухпозиционного регулятора требуется провести дополнительные несложные расчеты.
[9]
Для уменьшения статической ошибки копирования в следящей системе предусмотрено интегрирующее звено по управляющему воздействию.
[10]
Пусть теперь для уменьшения статической ошибки в систему вводится воздействие по производной.
[11]
Например, для уменьшения статической ошибки и улучшения динамических характеристик необходимо увеличивать коэффициент усиления, однако это ухудшает устойчивость системы. Поэтому для усилительной схемы задаются предельные значения / Ста и / тт, при достижении которых возникает отказ.
[13]
Это всегда полезно для уменьшения статической ошибки, ибо в этом случае при изменении нагрузки остаточное отклонение регулируемого параметра будет меньше. Обычно пропорциональные регуляторы применяются с объектами средней емкости при малых запаздываниях и малых изменениях нагрузки.
[14]
Есть, однако, способ уменьшения статической ошибки системы вне зависимости от динамических ее свойств.
[15]
Страницы:
1
2
3
Описания линейной системы в статике
можно получить, зная передаточную
функцию системы. Поскольку
структурные схемы в статике можно
получить из структурных схем в динамике,
заменив передаточные функции звеньев
статическими коэффициентами передачи,
найденными по этой формуле.
Правила структурных схем, справедливые
для динамики, можно применить и для
структурных преобразований в статике.
Качество систем автоматического
регулирования в статике определяется
статической ошибкой — разница между
заданным и действительным значениями
регулируемой величины в установившемся
режиме.
Пусть структурная схема САР в статике
имеет вид:
По определению
статическая ошибка =xуст
–yуст.
Найдем Δ через параметры системы
Тогда
где kр·kо
=kраз– статический коэффициент передачи
разомкнутой системы.
Тогда
зависит не только от параметров системы,
но и от входного сигнала.
Поэтому для оценки качества САР применяют
относительную статическую ошибку –
статизм, которую определяют как отношение
абсолютной статический ошибки к заданному
значению регулируемой величины.
Качество
системы в статике тем лучше, чем меньше
статическая ошибка, которая зависит от
величины kраз.
Для уменьшения статической ошибки
нужно:
-
Увеличивать
kраз.
Однако увеличениеkраз
ведёт к уменьшению запаса устойчивости
поэтому увеличиватьkразнужно очень осторожно; -
Включать
в прямую цепь регулирования астатическое
(интегрирующее) звено.
Астатическое звено уменьшает статическую
ошибку системы до 0. Систему с нулевой
статической ошибкой (при отсутствии
остаточного отклонения между заданным
и действительным значениями регулируемой
величины) называется астатической.
Система с наличием статической ошибки
(при наличии остаточного отклонения
между заданным и действительным
значениями регулируемой величины)
называется статической.
Тема 4 Устойчивость систем автоматического регулирования §1. Физическое и математическое определение устойчивости.
Система автоматического регулирования
называется устойчивой, если после снятия
возмущающего воздействия, которое
вывело её из состояния равновесия, она
вновь возвращается в состояние равновесия.
Если система не возвращается в состояние
равновесия после снятия возмущения,
она неустойчива.
устойчивая система (кривые 1, 2)
неустойчивая (3).
Для
определения математического условия
устойчивости САР необходимо решить
дифференциальное уравнение системы,
когда правая часть этого уравнения
равна 0 (при снятии возмущающего
воздействия), и посмотреть, как ведетyвых
(t) при t.
Пусть
Тогда дифференциальное уравнение
системы в операторной форме:
αnpny(p)
+ … +α1py(p)
+αoy(p)
=bmpmx(p)
+ …+ b1px(p)
+ box(p)
Оригинал дифференциального уравнения:
Для определения устойчивости системы,
описываемой этим уравнением, снимем
возмущения x(t)=0 и решим уравнение:
Для этого запишем характеристическое
уравнение:
H(p)
= αnpn
+ …. +α1p+αo= 0.
Как видно из последнего выражения,
характеристическое уравнение звена
или системы – это знаменатель передаточной
функции звена или системы, приравненный
к нулю.
Если p1,p2,
…,pn– корни характеристического уравнения,
то решение этого уравнения имеет вид:
гдеCi– постоянные интегрирования, определяемые
из начальных условий.
Рассмотрим отдельные случаи решения
дифференциального уравнения:
1)p1,p2,
…,pn– отрицательные действительные корни:pi=
-i.
Решение уравнения в этом случае:
.
2) p1,p2,
…,pn— положительные действительные корни:pi=
+i.
–
решение уравнения в этом случае
.
3) p1,p2,
…,pn— корни комплексно-сопряженные с
отрицательной вещественной частью:
pi
= – i
ji
.
4) p1,p2,
…,pn– корни комплексно-сопряженные с
положительной вещественной частью:
pi
= + i
ji
Анализируя все случаи решения
дифференциального уравнения для случая
x(t) = 0, можно сделать вывод:
система
автоматического регулирования устойчива,
если все корни ее характеристического
уравнения отрицательные действительные
или комплексно-сопряженные с отрицательной
действительной частью. Если же среди
корней характеристического уравнения
системы имеется хотя бы один положительный
действительный корень или хотя бы одна
пара комплексно-сопряженных корней с
положительной вещественной частью,
такая система неустойчива.
Математические правила, позволяющие
определить знаки корней алгебраического
(характеристического) уравнения, не
решая это уравнение, в ТАУ называют
критериями устойчивости.
Уменьшение — статическая ошибка
Cтраница 1
Уменьшение статической ошибки осуществляется в результате воздействия астатической части, которая в основном и служит для ликвидации остаточного отклонения. По окончании переходного режима движок реостатного датчика Rn и контактные группы обоих реле приходят в первоначальное среднее положение, соответствующее заданному значению регулируемой величины, а положение рукоятки спаренных сопротивлений Л5 и jR6 ( ручная перестановка) показывает степень открытия регулирующего органа в процентах от полного открытия. Доля участия пропорциональной части в перемещении регулирующего органа зависит от диапазона пропорциональности, а астатической — от величины времени действия астатической части.
[1]
Уменьшение статической ошибки ( а при бесконечно большом коэффициенте усиления усилителя в статике — сведение ее к нулю) в контуре с объектом в виде интегрирующего звена может быть достигнуто путем использования ПИ-регулятора.
[2]
Уменьшение статической ошибки регулирования ( ошибки в установившемся режиме) и увеличение быстродействия системы может быть достигнуто последовательным включением в контур системы автоматического регулирования ( в регулятор, осуществляющий основную обратную связь) безынерционного звена с большим коэффициентом усиления.
[4]
Для уменьшения статической ошибки используют также принцип управления по возмущению в комбинированных САУ. Из множества возмущений выбирается главное, и на основе его измерения воздействуют на объект управления таким образом, чтобы компенсировать естественное влияние данного воздействия на выходную величину системы. Комбинированные системы позволяют значительно снизить статическую ошибку системы без повышения степени ее астатизма.
[5]
Для уменьшения статической ошибки без нарушения устойчивости необходимо в контур ввести первую производную от входного сигнала.
[6]
Для уменьшения статической ошибки следует увеличивать коэффициент усиления системы. До известной степени можно считать, что величина коэффициента усиления определяется требованиями к величине статической ошибки.
[8]
Для уменьшения статической ошибки регулирования иногда рекомендуется установка в приточной камере двух калориферов ( или двух рядов калориферов), соединенных последовательно по воздуху и параллельно по воде. Теплопроизводительность каждого калорифера составляет приблизительно 50 % от общей требуемой. Благодаря этому статическая ошибка выбранного регулятора может быть снижена вдвое, так как регулируется только половина нагрузки. Первый по ходу воздуха калорифер отключается двухпозиционным регулятором, датчик которого установлен на входе наружного воздуха, когда достигается температура, при которой требуется только половина нагрузки — теплопроизводительности. Для определения температуры настройки двухпозиционного регулятора требуется провести дополнительные несложные расчеты.
[9]
Для уменьшения статической ошибки копирования в следящей системе предусмотрено интегрирующее звено по управляющему воздействию.
[10]
Пусть теперь для уменьшения статической ошибки в систему вводится воздействие по производной.
[11]
Например, для уменьшения статической ошибки и улучшения динамических характеристик необходимо увеличивать коэффициент усиления, однако это ухудшает устойчивость системы. Поэтому для усилительной схемы задаются предельные значения / Ста и / тт, при достижении которых возникает отказ.
[13]
Это всегда полезно для уменьшения статической ошибки, ибо в этом случае при изменении нагрузки остаточное отклонение регулируемого параметра будет меньше. Обычно пропорциональные регуляторы применяются с объектами средней емкости при малых запаздываниях и малых изменениях нагрузки.
[14]
Есть, однако, способ уменьшения статической ошибки системы вне зависимости от динамических ее свойств.
[15]
Страницы:
1
2
3
Существует
класс объектов, требующих точной настройки и не допускающих сбоев или нарушений
в работе. С этих позиций, а также на основании требований ТЗ, рассчитывают
коэффициенты регулятора с допустимой погрешностью. Однако ни одна модель точно
не соответствует реальному объекту. С этой позиции можно сделать вывод, что
реализованный регулятор может и не давать в реальной системе удовлетворительный
или оптимальный переходный процесс. Поэтому необходимо иметь грубые настройки
регулятора, которые при малых девиациях параметров объекта сильно не нарушали
качества переходного процесса. Поэтому регулятор необходимо реализовывать с
возможностью подстройки его параметров к реальной системе.
8. Структурное и параметрическое
обеспечение требуемой статической и динамической точности САУ. Какие виды
погрешностей не входят в понятие «статическая ошибка» (примеры). Пример
вычисления статической ошибки по возмущению и по управлению.
Повышение точности: 1) увеличение коэф усиления разомкнутой цепи,
2) повышение степени астатизма, 3) применение регулирования по производным от
ошибок. структурно надо ввести как минимум ПИ-регулятор
П – регулятор
уменьшает статическую ошибку. При чрезмерном увеличении К система становится
неустойчивой.
∆=1/(1+КК0).
И – регулятор снижает
запас устойчивости, и привносит сдвиг фаз -900.
∆=0
Д – регулятор
увеличивает запас устойчивости системы, и привносит сдвиг фаз +900.
Для
астатических систем характерным является режим линейной заводки, когда V=at. В этом случае
возникает «скоростная» ошибка ∆0с = a/КК0. Эта ошибка
уменьшается, если увеличивать К0.
В следящих системах
статическая ошибка порождается возмущением и уменьшается путем увеличения К0. В
режиме линейной заводки скоростная ошибка ∆0с = a/КК0 – М/К0
уменьшается путем увеличения К0. Однако уменьшить в данном случае скоростную
ошибку до 0 нельзя, т.к. наличие 2-х или более интеграторов в системе делают ее
структурно неустойчивой.
9. Определение величин: ошибка САУ,
погрешность систем, точность систем, погрешность на интервале усреднения.
Статическая ошибка, статическая погрешность – определение, отличия. Правило
суммирования погрешностей.
Статическая ошибка –
постоянное отклонение выходной величины от задающего воздействия в
установившемся режиме.
Рассмотрим
одноканальную систему стабилизации, для которой входное воздействие является
постоянной величиной, а цель регулирования состоит в организации свойства limy(t)=V.
Для оценки точности
используется ошибка регулирования ∆(t)= V- y(t), которая с
течением времени стремится к некоторому постоянному значению – статической
ошибке.
∆0=lim∆(t). По известной
структурной схеме системы ошибку можно определить в операторной форме с
помощью структурных преобразований ∆(p) = V(p) – y(p). В этом случае
статический режим характеризуется тем, что p=0, а статическая
ошибка находится по выражению ∆0 = ∆(0).
Динамической
ошибкой будем называть величину ∆d(t)=∆(t)-∆0,
причем lim∆d(t)=0.
Наличие статической
ошибки в общем случае является нежелательным, так как создается погрешность
управления. Но для полного устранения статического отклонения требуется до
бесконечности увеличивать коэффициент передачи k, что нереализуемо по ряду
причин (например, по условию обеспечения устойчивости). Таким образом, в
статической САУ принципиально нельзя полностью устранить статическую ошибку.
Статическими
погрешностями измерения называют погрешности, возникающие при определении
постоянного во времени измеряемого значения. При этом предполагается, что все
переходные процессы в измерительном устройстве завершены; следовательно,
измерительный прибор и измеряемая величина находятся в установившемся
состоянии. Если проводить многократные независимые измерения, то возникает
ситуация, при которой можно различать два принципиально отличающихся вида
погрешностей, так называемые систематические и случайные погрешности.
Систематическую погрешность Еs определяют как отклонение действительного
измеряемого значения от среднего значения (математического ожидания) : Еs = μ
– х. Математическое ожидание μ является средним значением бесконечного числа
измерений: . При одинаковых условиях эта погрешность всегда имеет ту
же самую абсолютную величину и тот же самый знак. В противоположность этому ни
абсолютная величина, ни знак отклонения отдельного измерения от математического
ожидания не могут быть предсказаны заранее. Эта случайная погрешность
обозначается через Еai.
Она соответствует разности между показанием единичного измерения и
математическим ожиданием: Еаi = хаi – μ.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание — внизу страницы.
2.1. Основные виды САУ
В зависимости от принципа и закона функционирования ЗУ, задающего программу изменения выходной величины,
различают основные виды САУ: системы стабилизации, программные, следящие
и самонастраивающиеся системы, среди которых можно выделить экстремальные,
оптимальные и адаптивные системы.
В системах стабилизации
(рис.9,10) обеспечивается неизменное значение управляемой величины при всех видах
возмущений, т.е. y(t) = const. ЗУ формирует эталонный сигнал, с которым
сравнивается выходная величина. ЗУ, как правило, допускает настройку эталонного
сигнала, что позволяет менять по желанию значение выходной величины.
В программных системах обеспечивается изменение управляемой величины
в соответствии с программой, формируемой ЗУ. В качестве ЗУ может использоваться
кулачковый механизм, устройство считывания с перфоленты или магнитной ленты
и т.п. К этому виду САУ можно отнести заводные игрушки, магнитофоны, проигрыватели
и т.п. Различают системы с временной программой (например, рис.1), обеспечивающие
y = f(t), и системы с пространственной программой, в которых y
= f(x), применяемые там, где на выходе САУ важно получить требуемую траекторию
в пространстве, например, в копировальном станке (рис.11), закон движения во
времени здесь роли не играет.
Следящие системы отличаются от программных лишь тем, что программа y = f(t)
или y = f(x) заранее неизвестна. В качестве ЗУ выступает устройство, следящее
за изменением какого-либо внешнего параметра. Эти изменения и будут определять
изменения выходной величины САУ. Например, рука робота, повторяющая движения руки
человека.
Все три рассмотренные вида САУ могут быть построены по любому
из трех фундаментальных принципов управления. Для них характерно требование совпадения
выходной величины с некоторым предписанным значением на входе САУ, которое само
может меняться. То есть в любой момент времени требуемое значение выходной величины
определено однозначно.
В самонастраивающихся системах ЗУ ищет такое
значение управляемой величины, которое в каком-то смысле является оптимальным.
Так в экстремальных системах (рис.12) требуется, чтобы выходная величина
всегда принимала экстремальное значение из всех возможных, которое заранее не
определено и может непредсказуемо изменяться. Для его поиска система выполняет
небольшие пробные движения и анализирует реакцию выходной величины на эти пробы.
После этого вырабатывается управляющее воздействие, приближающее выходную величину
к экстремальному значению. Процесс повторяется непрерывно. Так как в данных
САУ происходит непрерывная оценка выходного параметра, то они выполняются только
в соответствии с третьим принципом управления: принципом обратной связи.
Оптимальные системы являются более сложным
вариантом экстремальных систем. Здесь происходит, как правило, сложная обработка
информации о характере изменения выходных величин и возмущений, о характере влияния
управляющих воздействий на выходные величины, может быть задействована теоретическая
информация, информация эвристического характера и т.п. Поэтому основным отличием
экстремальных систем является наличие ЭВМ. Эти системы могут работать в соответствии
с любым из трех фундаментальных принципов управления.
В адаптивных системах
предусмотрена возможность автоматической перенастройки параметров или изменения
принципиальной схемы САУ с целью приспособления к изменяющимся внешним условиям.
В соответствии с этим различают самонастраивающиеся и самоорганизующиеся
адаптивные системы.
Все виды САУ обеспечивают совпадение выходной величины
с требуемым значением. Отличие лишь в программе изменения требуемого значения.
Поэтому основы ТАУ строятся на анализе самых простых систем: систем стабилизации.
Научившись анализировать динамические свойства САУ, мы учтем все особенности более
сложных видов САУ.
2.2. Статические характеристики
Режим работы САУ, в котором управляемая величина и все промежуточные величины не изменяются
во времени, называется установившимся, или статическим режимом.
Любое звено и САУ в целом в данном режиме описывается уравнениями статики
вида y = F(u,f), в которых отсутствует время t. Соответствующие
им графики называются статическими характеристиками. Статическая характеристика
звена с одним входом u может быть представлена кривой y = F(u) (рис.13).
Если звено имеет второй вход по возмущению f, то статическая характеристика
задается семейством кривых y = F(u) при различных значениях f, или
y = F(f) при различных u.
Так примером одного из функциональных звеньев системы регулирования воды в
баке (см. выше) является обычный рычаг (рис.14). Уравнение статики для него
имеет вид y = Ku. Его можно изобразить звеном, функцией которого является
усиление (или ослабление) входного сигнала в K раз. Коэффициент K
= y/u, равный отношению выходной величины к входной называется коэффициентом
усиления звена. Когда входная и выходная величины имеют разную природу,
его называют коэффициентом передачи.
Статическая характеристика данного звена имеет вид отрезка прямой линии с наклоном
a = arctg(L2/L1)
= arctg(K) (рис.15). Звенья с линейными статическими характеристиками называются
линейными. Статические характеристики реальных звеньев, как правило,
нелинейны. Такие звенья называются нелинейными. Для них характерна зависимость
коэффициента передачи от величины входного сигнала: K = y/
u
const.
Например,
статическая характеристика насыщенного генератора постоянного тока представлена
на рис.16. Обычно нелинейная характеристика не может быть выражена какой-либо
математической зависимостью и ее приходится задавать таблично или графически.
Зная статические характеристики
отдельных звеньев, можно построить статическую характеристику САУ (рис.17, 18).
Если все звенья САУ линейные, то САУ имеет линейную статическую характеристику
и называется линейной. Если хотя бы одно звено нелинейное, то САУ нелинейная.
Звенья, для которых можно задать статическую характеристику в виде жесткой функциональной
зависимости выходной величины от входной, называются статическими. Если
такая связь отсутствует и каждому значению входной величины соответствует множество
значений выходной величины, то такое звено называется астатическим. Изображать
его статическую характеристику бессмысленно. Примером астатического звена может
служить двигатель, входной величиной которого является напряжение U, а
выходной — угол поворота вала ,
величина которого при U = const может принимать любые значения. Выходная
величина астатического звена даже в установившемся режиме является функцией времени.
2.3. Статическое и астатическое регулирование
Если на управляемый процесс действует возмущение f, то важное значение имеет статическая характеристика САУ
в форме y = F(f) при yo = const.
Возможны два характерных вида этих характеристик (рис.19). В соответствии с
тем, какая из двух характеристик свойственна для данной САУ, различают статическое
и астатическое регулирование.
Рассмотрим систему регулирования уровня воды в баке (рис.20). Возмущающим фактора
является поток Q воды из бака. Пусть при Q = 0 имеем y = yo
, e = 0. ЗУ системы настраивается так, чтобы вода при этом не поступала.
При Q0,
уровень воды понижается (e0),
поплавок опускается и открывает заслонку, в бак начинает поступать вода. Новое
состояние равновесия достигается при равенстве входящего и выходящего потоков
воды. Но в любом случае при Q0
заслонка должна быть обязательно открыта, что возможно только при e0.
Причем, чем больше Q, тем при больших значениях e, устанавливается новое равновесное
состояние. Статическая характеристика САУ имеет характерный наклон (рис.19б).
Это есть пример статического регулирования. Для получения статического
регулирование, все звенья САР должны быть статическими.
Статические регуляторы работают при обязательном отклонении e регулируемой величины от требуемого значения.
Это отклонение тем больше, чем больше возмущение f. Это заложено в принципе
действия регулятора и не является его погрешностью, поэтому данное отклонение
называется статической ошибкой регулятора. Из рис.21 видно, что, чем
больше коэффициент передачи регулятора Kр, тем на большую
величину откроется заслонка при одних и тех же значениях e, обеспечив
в установившемся режиме большую величину потока Q. Это значит, что на
статической характеристике одинаковым значениям e при больших Kр
будут соответствовать большие значения возмущения Q, статическая характеристика
САУ пойдет более полого. Поэтому, чтобы уменьшить статическую ошибку надо
увеличивать коэффициент передачи регулятора. Того же результата можно добиться,
увеличивая коэффициент передачи объекта управления, но это дело конструкторов,
проектирующих данный объект, а не специалистов по автоматике.
Статизм d, САР, характеризует
насколько сильно значение регулируемой величины отклоняется от требуемого значения
при действии возмущений, и равна тангенсу угла наклона статической характеристики,
построенной в относительных единицах:
d = tg(a) = (рис.22),
где y =
yн, f = fн
— точка номинального режима САУ. При достаточно больших значениях Kp
имеем d
1/Kp.
В некоторых случаях статическая
ошибка недопустима, тогда переходят к астатическому регулированию, при
котором регулируемая величина в установившемся режиме принимает точно требуемое
значение независимо от величины возмущающего фактора. Статическая характеристика
астатической САУ не имеет наклона (рис.19в). Возможные неточности относятся
к погрешностям конкретной системы и не являются закономерными.
Для того, чтобы получить астатическое
регулирование, необходимо в регулятор включить астатическое звено, например
ИД, между ЧЭ и УО (рис.23).
Если уровень воды понизится, то поплавок переместит движок потенциометра на
величину L,
за счет этого появится разность потенциалов 0
и ИД начнет поднимать заслонку до тех пор, пока
не уменьшится до нуля, а это возможно только при y = yo
. При поднятии уровня воды разность потенциалов сменит знак, и двигатель будет
вращаться в противоположную сторону, опуская заслонку.
Достоинства и недостатки статического и астатического регулирования: статические регуляторы
обладают статической ошибкой; астатические регуляторы статической ошибки не имеют,
но они более инерционны, сложны конструктивно и более дороги.
Обеспечение
требуемой статической точности регулирования является первой основной задачей
при расчете элементов САУ.
Вопросы
- Перечислите
и дайте краткую характеристику основных видов САУ? - Что называется статическим
режимом САУ? - Что называется статическими характеристиками САУ?
- Что
называется уравнением статики САУ? - Что называется коэффициентом передачи,
в чем отличие от коэффициента усиления? - В чем отличие нелинейных звеньев
от линейных? - Как построить статическую характеристику нескольких звеньев?
- В
чем отличие астатических звеньев от статических? - В чем отличие астатического
регулирования от статического? - Как сделать статическую САР астатической?
- Что
называется статической ошибкой регулятора, как ее уменьшить? - Что называется
статизмом САР? - Назовите достоинства и недостатки статического и астатического
регулирования?
Далее…
Я добрался до кода широко известного клиента мгновенных сообщений Miranda IM. Вместе с различными плагинами это достаточно большой проект, размер которого составляет около 950 тысяч строк кода на C и C++. И, как в любом солидном проекте с историей развития, в нем имеется немалое количество ошибок и опечаток.
Рассматривая дефекты в различных приложениях, я заметил некоторые закономерности. И сейчас на примере дефектов, найденных в Miranda IM, я попробую сформулировать некоторые рекомендации, которые позволят избежать многих ошибок и опечаток ещё на этапе написания кода.
Для анализа Miranda IM я использовал (да, вы уже угадали) анализатор PVS-Studio 4.14. Код проекта Miranda IM весьма качественен, что подтверждается популярностью этой программы. Я и сам являюсь пользователем этого клиента и не имею к нему претензий в плане качества. Проект собирается в Visual Studio с Warning Level 3 (/W3), а количество комментариев составляет 20% от всего текста программы.
1. Избегайте функции memset, memcpy, ZeroMemory и им аналогичные
Мы начнем с ошибок, возникающих при использовании низкоуровневых функций работы с памятью такими, как memset, memcpy, ZeroMemory и им подобных.
Рекомендую всячески избегать этих функций. Понятно, что не стоит буквально следовать моему совету и заменять все эти функции на циклы. Но я видел такое огромное количество ошибок, связанных с использованием этих функций, что очень рекомендую быть аккуратными с ними и использовать только по необходимости. На мой взгляд есть только два случая, когда использование этих функций оправдано:
1) Обработка больших массивов. То есть там, где оптимизированный алгоритм функции даст выигрыш по сравнению с обработкой элементов в простом цикле.
2) Обработка большого количества маленьких массивов. Также имеет смысл с точки зрения улучшения производительности.
Во всех остальных случаях лучше попробовать обойтись без них. Например, я считаю эти функции излишними в такой программе, как Miranda. Никаких ресурсоёмких алгоритмов или огромных массивов в ней нет. Следовательно, использование функций memset/memcpy проистекает только из удобства написания короткого кода. Однако, эта простота очень обманчива и, сэкономив несколько секунд на написании кода, можно неделями вылавливать нечётко проявляющуюся ошибку порчи памяти. Рассмотрим несколько примеров кода, взятых из проекта Miranda IM.
V512 A call of the ‘memcpy’ function will lead to a buffer overflow or underflow. tabsrmm utils.cpp 1080
typedef struct _textrangew { CHARRANGE chrg; LPWSTR lpstrText; } TEXTRANGEW; const wchar_t* Utils::extractURLFromRichEdit(...) { ... ::CopyMemory(tr.lpstrText, L"mailto:", 7); ... }
Копируем только кусок строки. Ошибка банальна до безобразия, но от этого не перестаёт быть ошибкой. Скорее всего, раньше здесь использовалась строка, состоящая из ‘char’. Затем перешли на Unicode строки, а поменять константу забыли.
Если изначально использовать для копирования строки функции, которые для этого и предназначаются, то такая ошибка просто не сможет возникнуть. Представим, что было написано так:
strncpy(tr.lpstrText, "mailto:", 7);
Тогда при переходе к Unicode строкам, число 7 изменять не надо:
wcsncpy(tr.lpstrText, L"mailto:", 7);
Я не говорю, что это идеальный код. Но он уже намного лучше, чем использование CopyMemory. Рассмотрим другой пример.
V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_modern modern_extraimage.cpp 302
void ExtraImage_SetAllExtraIcons(HWND hwndList,HANDLE hContact) { ... char *(ImgIndex[64]); ... memset(&ImgIndex,0,sizeof(&ImgIndex)); ... }
Здесь хотелось обнулить массив, состоящий из 64-указателей. Но вместо этого мы обнулим только первый элемент. Эта же ошибка, кстати, присутствует ещё раз в другом файле. Скажем спасибо Copy-Paste:
V568 It’s odd that the argument of sizeof() operator is the ‘& ImgIndex’ expression. clist_mw extraimage.c 295
Корректный вариант кода должен выглядеть следующим образом:
memset(&ImgIndex,0,sizeof(ImgIndex));
Кстати, взятие адреса у массива может только дополнительно запутать того, кто читает код. Здесь взятие адреса не имеет никакого эффекта. И код может быть написан так:
memset(ImgIndex,0,sizeof(ImgIndex));
Следующий пример.
V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 258
static ROWCELL* rowOptTA[100]; void rowOptAddContainer(HWND htree, HTREEITEM hti) { ... ZeroMemory(rowOptTA,sizeof(&rowOptTA)); ... }
Снова, вместо вычисления размера массива, мы вычисляем размер указателя. Правильным выражением будет «sizeof(rowOptTA)». Для очистки массива я предлагаю следующий вариант такого кода:
const size_t ArraySize = 100; static ROWCELL* rowOptTA[ArraySize]; ... std::fill(rowOptTA, rowOptTA + ArraySize, nullptr);
Я уже привык, что подобные строки любят размножаться по программе копированием:
V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 308
V568 It’s odd that the argument of sizeof() operator is the ‘& rowOptTA’ expression. clist_modern modern_rowtemplateopt.cpp 438
Вы думаете, что это всё касательно низкоуровневой работы с масивами? Нет, не всё. Смотрите дальше, бойтесь и бейте по рукам любителей написать memset.
V512 A call of the ‘memset’ function will lead to a buffer overflow or underflow. clist_modern modern_image_array.cpp 59
static BOOL ImageArray_Alloc(LP_IMAGE_ARRAY_DATA iad, int size) { ... memset(&iad->nodes[iad->nodes_allocated_size], (size_grow - iad->nodes_allocated_size) * sizeof(IMAGE_ARRAY_DATA_NODE), 0); ... }
В этот раз размер копируемых данных вычислен корректно. Вот только перепутан местами второй и третий аргумент. Как следствие, мы заполняем 0 элементов. Корректный вариант:
memset(&iad->nodes[iad->nodes_allocated_size], 0, (size_grow - iad->nodes_allocated_size) * sizeof(IMAGE_ARRAY_DATA_NODE));
Как переписать этот фрагмент кода более изящно, я не знаю. Вернее, его нельзя переписать красиво, не затрагивая другие участки программы и структуры данных.
Возникает вопрос, а как же обойтись без memset при работе с такими структурами, как OPENFILENAME:
OPENFILENAME x; memset(&x, 0, sizeof(x));
Очень просто. Создать обнуленную структуру можно так:
OPENFILENAME x = { 0 };
2. Внимательно следите, работаете вы со знаковым или беззнаковым типом
Проблема путаницы между signed и unsigned типами может на первый взгляд показаться надуманной. Но этот вопрос зря недооценивается программистами.
В большинстве случаев, людям не нравится рассматривать предупреждения компилятора, связанные с тем, что переменная типа int сравнивается с переменной типа unsigned. И действительно, чаще всего подобный код совершенно корректен. Программисты отключают такие предупреждения или не обращают на них внимания. Или есть третий вариант, они вписывают явное приведение типа, чтобы заглушить предупреждение компилятора, не вдаваясь в подробности.
Предлагаю больше так не делать и каждый раз анализировать ситуацию, когда знаковый тип встречается с беззнаковым. И, вообще, быть внимательным к тому, какой тип имеет выражение или что возвращает функция. Теперь несколько примеров по рассматриваемой теме.
V547 Expression ‘wParam >= 0’ is always true. Unsigned type value is always >= 0. clist_mw cluiframes.c 3140
В коде программы имеется функция id2pos, которая возвращает значение ‘-1’ в случае ошибки. С этой функцией всё в порядке. В другом месте результат работы функции id2pos используется следующим образом:
typedef UINT_PTR WPARAM; static int id2pos(int id); static int nFramescount=0; INT_PTR CLUIFrameSetFloat(WPARAM wParam,LPARAM lParam) { ... wParam=id2pos(wParam); if(wParam>=0&&(int)wParam<nFramescount) if (Frames[wParam].floating) ... }
Проблема в том, что переменная wParam имеет беззнаковый тип. Следовательно, условие ‘wParam>=0’ всегда истинно. Если функция id2pos вернет ‘-1’, то условие проверки недопустимых значений не сработает, и мы начнем использовать отрицательный индекс.
Я почти уверен, что вначале был написан следующий код:
if (wParam>=0 && wParam<nFramescount)
Компилятор Visual C++ выдал предупреждение «warning C4018: ‘<‘: signed/unsigned mismatch». Это предупреждение как раз включено на Warning Level 3, с которым и собирается Miranda IM. И в этот момент этому месту было уделено недостаточное внимание. Предупреждение было подавлено явным приведением типа. Но ошибка не исчезла, а только спряталась. Корректным вариантом должен был стать следующий код:
if ((int)wParam>=0 && (int)wParam<nFramescount)
Так что внимание и ещё раз внимание к подобным местам. В Miranda IM я насчитал 33 условия, которые из-за путаницы с signed/unsigned всегда истинны или всегда ложны.
Продолжим. Следующий пример мне особенно нравится. А комментарий, просто прекрасен.
V547 Expression ‘nOldLength < 0’ is always false. Unsigned type value is never < 0. IRC mstring.h 229
void Append( PCXSTR pszSrc, int nLength ) { ... UINT nOldLength = GetLength(); if (nOldLength < 0) { // protects from underflow nOldLength = 0; } ... }
Думаю, пояснений к этому коду не требуется.
Конечно, в ошибках не всегда виноваты только программисты. Иногда свинью подкладывают и разработчики библиотек (в данном случае WinAPI).
#define SRMSGSET_LIMITNAMESLEN_MIN 0 static INT_PTR CALLBACK DlgProcTabsOptions(...) { ... limitLength = GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >= SRMSGSET_LIMITNAMESLEN_MIN ? GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) : SRMSGSET_LIMITNAMESLEN_MIN; ... }
Если не обращать внимания на излишне сложное выражение, то код выглядит корректно. Кстати, это вообще была одна строка. Для удобства я разбил её на несколько. Впрочем, форматирование кода сейчас мы затрагивать не будем.
Проблема в том, что функция GetDlgItemInt() возвращает вовсе не ‘int’, как ожидал программист. Эта функция возвращает UINT. Вот её прототип из файла «WinUser.h»:
WINUSERAPI UINT WINAPI GetDlgItemInt( __in HWND hDlg, __in int nIDDlgItem, __out_opt BOOL *lpTranslated, __in BOOL bSigned);
В результате PVS-Studio выдает сообщение:
V547 Expression is always true. Unsigned type value is always >= 0. scriver msgoptions.c 458
И это действительно так. Выражение «GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >= SRMSGSET_LIMITNAMESLEN_MIN» всегда истинно.
Конкретно в данном случае, возможно, никакой ошибки и не будет. Но смысл моего предупреждения, надеюсь, понятен. Внимательно смотрите, что возвращают вам функции.
3. Избегайте большого количества вычислений в одной строке
Каждый программист знает и при обсуждениях ответственно заявляет, что надо писать простой и понятный код. Но на практике иногда кажется, что он участвует в тайном конкурсе, кто напишет строку похитрее, с использованием интересной конструкции языка или продемонстрирует умение жонглировать указателями.
Чаще всего ошибки возникают там, где программист, с целью создать компактный код, собирает в одну строку сразу несколько действий. При незначительном выигрыше в изящности он существенно увеличивает риск опечатки или того, что не учтёт побочные действия. Рассмотрим пример:
V567 Undefined behavior. The ‘s’ variable is modified while being used twice between sequence points. msn ezxml.c 371
short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len) { ... while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') { ... }
Здесь возникает неопределенное поведение (undefined behavior). Этот код может корректно функционировать в течение долгого периода жизни программы. Но нет никакой гарантии, что он также будет вести себя после смены версии компилятора или ключей оптимизации. Компилятор в праве вначале вычислить ‘++s’, а затем взывать функцию ‘strspn(s, EZXML_WS)’. Или, наоборот, вначале он может вызвать функцию, а уже затем увеличить значение переменной ‘s’.
Приведу другой пример, почему не стоит пытаться собрать всё в одну строку. В Miranda IM некоторые ветви исполнения программы отключены/включены с помощью вставок вида ‘&& 0’. Примеры:
if ((1 || altDraw) && ... if (g_CluiData.bCurrentAlpha==GoalAlpha &&0) if(checkboxWidth && (subindex==-1 ||1)) {
С приведенными сравнениями всё ясно и они хорошо заметны. А теперь представьте, что вы встречаете фрагмент показанный ниже. Код я отформатировал. В программе это ОДНА строка.
V560 A part of conditional expression is always false: 0. clist_modern modern_clui.cpp 2979
LRESULT CLUI::OnDrawItem( UINT msg, WPARAM wParam, LPARAM lParam ) { ... DrawState(dis->hDC,NULL,NULL,(LPARAM)hIcon,0, dis->rcItem.right+dis->rcItem.left- GetSystemMetrics(SM_CXSMICON))/2+dx, (dis->rcItem.bottom+dis->rcItem.top- GetSystemMetrics(SM_CYSMICON))/2+dx, 0,0, DST_ICON| (dis->itemState&ODS_INACTIVE&&FALSE?DSS_DISABLED:DSS_NORMAL)); ... }
Если здесь нет ошибки, то всё равно непросто вспомнить и найти в этой строке слово FALSE. Вы его нашли? Согласитесь — непростая задача. А если это ошибка? Тогда вообще нет шансов найти её, просто просматривая код. Подобные выражения очень полезно выносить отдельно. Пример:
UINT uFlags = DST_ICON; uFlags |= dis->itemState & ODS_INACTIVE && FALSE ? DSS_DISABLED : DSS_NORMAL;
А я сам, пожалуй, написал бы это длинным, но более понятным способом:
UINT uFlags; if (dis->itemState & ODS_INACTIVE && (((FALSE)))) uFlags = DST_ICON | DSS_DISABLED; else uFlags = DST_ICON | DSS_NORMAL;
Да, это длиннее, но зато легко читается, а слово FALSE лучше заметно.
4. Выравнивайте в коде всё, что возможно
Выравнивание кода уменьшает вероятность допустить опечатку или ошибки при Copy-Paste. Если ошибка все же будет сделана, то найти её в процессе обзора кода будет в несколько раз проще. Рассмотрим пример кода.
V537 Consider reviewing the correctness of ‘maxX’ item’s usage. clist_modern modern_skinengine.cpp 2898
static BOOL ske_DrawTextEffect(...) { ... minX=max(0,minX+mcLeftStart-2); minY=max(0,minY+mcTopStart-2); maxX=min((int)width,maxX+mcRightEnd-1); maxY=min((int)height,maxX+mcBottomEnd-1); ... }
Монолитный фрагмент кода, читать который совершенно не интересно. Отформатируем его:
minX = max(0, minX + mcLeftStart - 2); minY = max(0, minY + mcTopStart - 2); maxX = min((int)width, maxX + mcRightEnd - 1); maxY = min((int)height, maxX + mcBottomEnd - 1);
Это не самый показательный пример, но согласитесь, теперь стало намного проще заметить, что два раза используется переменная maxX.
Мою рекомендацию по выравниванию не стоит понимать буквально и везде строить столбцы кода. Во-первых, это требует дополнительного времени при написании и правке кода. Во-вторых, это может привести к другим ошибкам. В следующем примере, как раз желание сделать красивый столбик привело к возникновению ошибки в коде Miranda IM.
V536 Be advised that the utilized constant value is represented by an octal form. Oct: 037, Dec: 31. msn msn_mime.cpp 192
static const struct _tag_cpltbl { unsigned cp; const char* mimecp; } cptbl[] = { { 037, "IBM037" }, // IBM EBCDIC US-Canada { 437, "IBM437" }, // OEM United States { 500, "IBM500" }, // IBM EBCDIC International { 708, "ASMO-708" }, // Arabic (ASMO 708) ... }
Желая сделать красивую колонку чисел, легко увлечься и вписать в начале ‘0’, сделав константу восьмеричной.
Уточняю рекомендацию. Выравнивайте в коде всё, что возможно. Но не выравнивайте числа, дописывая нули.
5. Не размножайте строку более, чем один раз
Копирование строк при программировании неизбежно. Но можно подстраховать себя, не вставляя строку из буфера обмена сразу несколько раз. В большинстве случаев лучше скопировать строку, затем отредактировать. Вновь скопировать и отредактировать. И так далее. Так трудней забыть что-то изменить в строке или изменить её неправильно. Рассмотрим пример кода:
V525 The code containing the collection of similar blocks. Check items ‘1316’, ‘1319’, ‘1318’, ‘1323’, ‘1323’, ‘1317’, ‘1321’ in lines 954, 955, 956, 957, 958, 959, 960. clist_modern modern_clcopts.cpp 954
static INT_PTR CALLBACK DlgProcTrayOpts(...) { ... EnableWindow(GetDlgItem(hwndDlg,IDC_PRIMARYSTATUS),TRUE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLETIMESPIN),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLETIME),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_ALWAYSPRIMARY),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_ALWAYSPRIMARY),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLE),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_MULTITRAY),FALSE); ... }
Скорее всего, никакой настоящей ошибки здесь нет. Просто два раза работаем с элементом IDC_ALWAYSPRIMARY. Тем не менее, ошибиться в подобных блоках из скопированных строк весьма легко.
6. Выставляйте высокий уровень предупреждений у компилятора и используйте статические анализаторы
По поводу многих ошибок невозможно дать рекомендаций, как снизить вероятность их появления в коде. Чаще всего они представляют собой опечатки, которые допускает как новичок, так и самый высококвалифицированный программист.
Тем не менее, многие из этих ошибок можно обнаружить ещё на этапе написания кода. В первую очередь — это предупреждения компилятора. А во вторую — отчеты от ночного запуска статических анализаторов кода.
Сейчас кто-то скажет, что это плохо скрываемая реклама. Но на самом деле это просто ещё одна рекомендация, которая позволяет сократить количество ошибок. Если я нашел ошибки, используя статический анализ, и не могу сказать, как избежать их появления в коде, то значит рекомендацией как раз и является использование анализаторов кода.
Рассмотрим некоторые примеры ошибок, которые могут быть оперативно выявлены анализаторами исходного кода:
V560 A part of conditional expression is always true: 0x01000. tabsrmm tools.cpp 1023
#define GC_UNICODE 0x01000 DWORD dwFlags; UINT CreateGCMenu(...) { ... if (iIndex == 1 && si->iType != GCW_SERVER && !(si->dwFlags && GC_UNICODE)) { ... }
Допущена опечатка. Вместо оператора ‘&’ используется оператор ‘&&’. Как здесь подстраховаться при написании кода, я не знаю. Корректный вариант условия:
(si->dwFlags & GC_UNICODE)
Следующий пример.
V528 It is odd that pointer to ‘char’ type is compared with the » value. Probably meant: *str != ». clist_modern modern_skinbutton.cpp 282
V528 It is odd that pointer to ‘char’ type is compared with the » value. Probably meant: *endstr != ». clist_modern modern_skinbutton.cpp 283
static char *_skipblank(char * str) { char * endstr=str+strlen(str); while ((*str==' ' || *str=='t') && str!='') str++; while ((*endstr==' ' || *endstr=='t') && endstr!='' && endstr<str) endstr--; ... }
В этом коде всего лишь забыты две звездочки ‘*’ для разыменования указателей. Результат может быть фатальным. Этот код предрасположен к access violation. Корректный вариант кода:
while ((*str==' ' || *str=='t') && *str!='') str++; while ((*endstr==' ' || *endstr=='t') && *endstr!='' && endstr<str) endstr--;
Здесь вновь сложно дать совет, кроме использования специальных инструментов для проверки кода.
Следующий пример.
V514 Dividing sizeof a pointer ‘sizeof (text)’ by another value. There is a probability of logical error presence. clist_modern modern_cachefuncs.cpp 567
#define SIZEOF(X) (sizeof(X)/sizeof(X[0])) int Cache_GetLineText(..., LPTSTR text, int text_size, ...) { ... tmi.printDateTime(pdnce->hTimeZone, _T("t"), text, SIZEOF(text), 0); ... }
На первый взгляд все хорошо. В функцию передается текст и его длина, посчитанная с помощью макроса SIZEOF. На самом деле макрос следует назвать COUNT_OF, но не в этом дело. Беда в том, что мы пытаемся посчитать количество символов в указателе. Здесь вычисляется «sizeof(LPTSTR) / sizeof(TCHAR)». Человек такие подозрительные места замечает плохо, а вот компилятор и статический анализатор хорошо. Исправленный вариант кода:
tmi.printDateTime(pdnce->hTimeZone, _T("t"), text, text_size, 0);
Следующий пример
V560 A part of conditional expression is always true: 0x29. icqoscar8 fam_03buddy.cpp 632
void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen) { ... else if (wTLVType = 0x29 && wTLVLen == sizeof(DWORD)) ... }
Здесь уместна рекомендация писать константу в условии на первом месте. Вот такой код просто не скомпилируется:
if (0x29 = wTLVType && sizeof(DWORD) == wTLVLen)
Но многим программистам, и мне в их числе, такой стиль не нравится. Например, мне он мешает читать код, так как вначале я хочу узнать, какую переменную сравнивают, а только потом с чем именно.
Если программист отказывается от такого стиля сравнений, то ему остается или полагаться на компилятор/анализатор, или рисковать.
Кстати, несмотря на то, что про эту ошибку все знают, она не самая редкая. Ещё три примера из Miranda IM, где анализатор PVS-Studio выдал предупреждение V559:
else if (ft->ft_magic = FT_MAGIC_OSCAR) if (ret=0) {return (0);} if (Drawing->type=CLCIT_CONTACT)
Анализ кода позволяет также выявить если и не ошибки, то очень подозрительные места в коде. Например, в Miranda IM указатели используют далеко не только как указатели. Если в одних местах кода такие игры выглядят нормально, то в других пугают. Пример кода, который меня крайне настораживает:
V542 Consider inspecting an odd type cast: ‘char *’ to ‘char’. clist_modern modern_toolbar.cpp 586
static void sttRegisterToolBarButton(..., char * pszButtonName, ...) { ... if ((BYTE)pszButtonName) tbb.tbbFlags=TBBF_FLEXSIZESEPARATOR; else tbb.tbbFlags=TBBF_ISSEPARATOR; ... }
Фактически мы проверяем, что адрес строки не кратен 256. Мне не понятно, что на самом деле хотели написать в условии. Возможно, это даже правильно, но сильно сомнительно.
Анализом кода можно найти немало некорректных условий. Пример:
V501 There are identical sub-expressions ‘user->statusMessage’ to the left and to the right of the ‘&&’ operator. jabber jabber_chat.cpp 214
void CJabberProto::GcLogShowInformation(...) { ... if (user->statusMessage && user->statusMessage) ... }
И так далее и так далее. Я могу ещё долго приводить другие примеры, но это не имеет смысла. Важно то, что большое количество ошибок можно обнаружить с помощью статического анализа на самых ранних этапах.
Когда в вашей программе статический анализатор находит мало ошибок, то его использование не кажется интересным. Но это неверный путь в рассуждениях. Ведь многие ошибки, которые анализатор смог бы найти на ранних этапах, вы исправляли потом и кровью, отлаживаясь часами.
Статический анализ представляет больший интерес в процессе разработки программы, а не в качестве разовых проверок. Множество ошибок и опечаток находится в процессе тестирования и создания юнит-тестов. Но если часть из этих ошибок можно найти ещё на этапе написания кода, то это будет колоссальный выигрыш времени и сил. Обидно два часа отлаживать программу, чтобы потом заметить лишнюю точку с запятой ‘;’ после оператора ‘for’. Такую ошибку можно часто обезвредить, потратив 10 минут на статический анализ измененных в процессе работы файлов.
Заключение
В этой статье я поделился только некоторыми мыслями по поводу того, как допускать меньше ошибок при программировании на Си++. Зреют и другие мысли, о которых я постараюсь написать в следующих статьях и заметках.
P.S.
Уже стало традицией, что после подобной статьи, кто-то спрашивает, а сообщили ли вы разработчикам программы/библиотеки о найденных ошибках. Заранее отвечу на вопрос, отправили ли мы баг репорт по проекту Miranda IM.
Нет, не отправили. Это слишком ресурсоёмкая задача. В статье показана только малая часть от того, что было найдено. В проекте около ста фрагментов, где стоит поправить код, и более ста, где я просто не знаю, ошибка это или нет. Однако, перевод этой статьи будет отправлен авторам Miranda IM и им будет предложена бесплатная версия анализатора PVS-Studio. Если они проявят интерес, то смогут сами проверить исходный код и исправить то, что сочтут нужным.
Ещё стоит пояснить, почему я часто не берусь судить ошибка тот или иной участок кода или нет. Пример неоднозначного кода:
V523 The ‘then’ statement is equivalent to the ‘else’ statement. scriver msglog.c 695
if ( streamData->isFirst ) { if (event->dwFlags & IEEDF_RTL) { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\rtlpar"); } else { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\ltrpar"); } } else { if (event->dwFlags & IEEDF_RTL) { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\rtlpar"); } else { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\ltrpar"); } }
Вот два одинаковых фрагмента кода. Возможно, это ошибка. А возможно, сейчас в любой ветке нужен одинаковый набор действий. И код написан специально так, чтобы потом его было просто модифицировать. Здесь необходимо знать программу, чтобы разобраться ошибочно это место или нет.