
بدافع من القدر ، قمنا بفحص معظم المكتبات في مجموعة "Awesome hpp". هذه هي مشاريع C ++ صغيرة تتكون فقط من ملفات الرأس. نأمل أن تساعد الأخطاء التي تم العثور عليها في جعل هذه المكتبات أفضل قليلاً. سنكون سعداء أيضًا إذا بدأ مؤلفوهم في استخدام محلل PVS-Studio مجانًا على أساس منتظم.
أوجه انتباهكم إلى نظرة عامة على نتائج فحص المكتبات المختلفة المدرجة في قائمة awesome-hpp (قائمة منسقة من مكتبات C ++ الرائعة فقط).
لقد علمت أولاً عن هذه القائمة من البودكاست " Cross Platform Mobile Telephony ". اغتنام هذه الفرصة ، أوصي جميع مبرمجي C ++ بالتعرف على CppCast . CppCast هو أول بودكاست لمطوري C ++ بواسطة مطوري C ++!
على الرغم من وجود عدد كبير من المشاريع في القائمة ، كانت هناك أخطاء قليلة جدًا. هناك ثلاثة أسباب لذلك:
- هذه مشاريع صغيرة جدا. يتكون العديد حرفيا من ملف رأس واحد.
- لم نتحقق من جميع المشاريع. كانت هناك مشاكل في تجميع بعضها وقررنا تخطيها.
- في كثير من الأحيان ، لفهم ما إذا كانت هناك أخطاء في فئات / وظائف القوالب أم لا ، يجب إنشاء مثيل لها. وفقًا لذلك ، لا يمكن للمحلل اكتشاف العديد من الأخطاء إلا في مشروع حقيقي ، عندما يتم استخدام المكتبة بنشاط. لقد قمنا فقط بتضمين ملفات الرأس في ملف .cpp فارغ وفحصناها ، مما يجعل عملية التحقق غير فعالة.
ومع ذلك ، أثناء دراسة التحذيرات ، كان هناك عدد كافٍ منهم لكتابة هذا المقال واثنين من التحذيرات الإضافية.
ملاحظة لزملائي
ملاحظة لمطوري المكتبة. يمكن للمهتمين استخدام محلل PVS-Studio مجانًا للتحقق من المشاريع مفتوحة المصدر. للحصول على ترخيص لمشروعك مفتوح المصدر ، يرجى ملء هذا النموذج .
الآن دعونا نلقي نظرة أخيرًا على ما تم العثور عليه في بعض المكتبات.
تم العثور على أخطاء
مكتبة Iutest
وصف موجز لمكتبة iutest :
iutest هو إطار عمل لكتابة اختبارات C ++.
template<typename Event>
pool_handler<Event> & assure() {
....
return static_cast<pool_handler<Event> &>(it == pools.cend() ?
*pools.emplace_back(new pool_handler<Event>{}) : **it);
....
}
تحذير PVS-Studio: V1023 تتم إضافة مؤشر بدون مالك إلى حاوية "المجمعات" بواسطة طريقة "emplace_back". يحدث تسرب للذاكرة في حالة حدوث استثناء. entt.hpp 17114
هذا الرمز لديه القدرة على تسرب الذاكرة. إذا احتاجت الحاوية إلى إعادة تخصيص ولا يمكنها تخصيص ذاكرة لصفيف جديد ، فسيتم طرح استثناء وسيفقد المؤشر.
ربما ، بالنسبة للاختبارات ، هذا الموقف غير محتمل وليس حرجًا. ومع ذلك ، قررت أن أذكر هذا النقص للأغراض التعليمية :).
الخيار الصحيح:
pools.emplace_back(std::make_unique<pool_handler<Event>>{})
مكان آخر مشابه: V1023 تتم إضافة مؤشر بدون مالك إلى حاوية "المجمعات" بواسطة طريقة "emplace_back". يحدث تسرب للذاكرة في حالة حدوث استثناء. entt.hpp 17407
مكتبة Jsoncons
وصف موجز لمكتبة jsoncons :
مكتبة C ++ ، رأس فقط لإنشاء تنسيقات بيانات JSON و JSON ، مع JSON Pointer و JSON Patch و JSONPath و JMESPath و CSV و MessagePack و CBOR و BSON و UBJSON.الخطأ الأول
static constexpr uint64_t basic_type_bits = sizeof(uint64_t) * 8;
uint64_t* data()
{
return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_;
}
basic_bigint& operator<<=( uint64_t k )
{
size_type q = (size_type)(k / basic_type_bits);
....
if ( k ) // 0 < k < basic_type_bits:
{
uint64_t k1 = basic_type_bits - k;
uint64_t mask = (1 << k) - 1; // <=
....
data()[i] |= (data()[i-1] >> k1) & mask;
....
}
reduce();
return *this;
}
تحذير PVS-Studio: V629 ضع في اعتبارك فحص التعبير "1 << k". تحويل البت لقيمة 32 بت مع التوسيع اللاحق لنوع 64 بت. bigint.hpp 744
تمت مناقشة هذا الخطأ بالتفصيل في المقالة " لماذا من المهم إجراء تحليل ثابت للمكتبات مفتوحة المصدر التي تضيفها إلى مشروعك ". باختصار ، للحصول على قيم القناع الصحيحة ، عليك أن تكتب مثل هذا:
uint64_t mask = (static_cast<uint64_t>(1) << k) - 1;
او مثل هذا:
uint64_t mask = (1ull << k) - 1;
يمكن مشاهدة الخطأ الأول تمامًا هنا: V629 ضع في اعتبارك فحص التعبير "1 << k". تحويل البت لقيمة 32 بت مع التوسيع اللاحق لنوع 64 بت. bigint.hpp 779
الخطأ الثاني
template <class CharT = typename std::iterator_traits<Iterator>::value_type>
typename std::enable_if<sizeof(CharT) == sizeof(uint16_t)>::type
next() UNICONS_NOEXCEPT
{
begin_ += length_;
if (begin_ != last_)
{
if (begin_ != last_)
{
....
}
تحذير PVS-Studio: فحص متكرر V571 . تم التحقق من حالة 'if (begin_! = Last_)' في السطر 1138. unicode_traits.hpp 1140
Strange retest . هناك شك في وجود خطأ مطبعي هنا ويجب أن يبدو الشرط الثاني مختلفًا.
مكتبة Clipp
وصف موجز لمكتبة القصاصات :
clipp - واجهات سطر الأوامر لـ C ++ الحديثة. معالجة حجة سطر الأوامر سهلة الاستخدام وقوية ومعبرة لـ C ++ 11/14/17 المضمنة في ملف رأس واحد.
inline bool
fwd_to_unsigned_int(const char*& s)
{
if(!s) return false;
for(; std::isspace(*s); ++s);
if(!s[0] || s[0] == '-') return false;
if(s[0] == '-') return false;
return true;
}
تحذير PVS-Studio: تعبير V547 [0] == '-' خطأ دائمًا. clipp.h 303
حسنًا ، في الواقع ، هذا ليس خطأ ، ولكنه مجرد رمز فائض عن الحاجة. يتم إجراء فحص الطرح مرتين.
مكتبة SimpleIni
وصف موجز لمكتبة SimpleIni :
مكتبة عبر الأنظمة الأساسية توفر واجهة برمجة تطبيقات بسيطة لقراءة ملفات التكوين بنمط INI وكتابتها. وهو يدعم ملفات البيانات في ASCII و MBCS و Unicode.
#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux))
تحذير PVS-Studio: V1040 خطأ مطبعي محتمل في تهجئة اسم ماكرو محدد مسبقًا. ماكرو '_linux' مشابه لـ '__linux'. SimpleIni.h 2923 على
الأرجح ، هناك شرطة سفلية واحدة مفقودة في اسم ماكرو لينوكس ويجب استخدام الاسم __linux . ومع ذلك ، في POSIX ، يتم إهمال هذا الماكرو ومن الأفضل استخدام __linux__ .
مكتبة محلل CSV
وصف موجز لمكتبة محلل CSV :
مكتبة C ++ حديثة لقراءة وكتابة وتحليل ملفات CSV (وملفات مماثلة).
CSV_INLINE void CSVReader::read_csv(const size_t& bytes) {
const size_t BUFFER_UPPER_LIMIT = std::min(bytes, (size_t)1000000);
std::unique_ptr<char[]> buffer(new char[BUFFER_UPPER_LIMIT]);
auto * HEDLEY_RESTRICT line_buffer = buffer.get();
line_buffer[0] = '\0';
....
this->feed_state->feed_buffer.push_back(
std::make_pair<>(std::move(buffer), line_buffer - buffer.get())); // <=
....
}
تحذير PVS-Studio: V769 مؤشر "buffer.get ()" في تعبير "line_buffer - buffer.get ()" يساوي nullptr. القيمة الناتجة لا معنى لها ولا ينبغي استخدامها. csv.hpp 4957
موقف مثير للاهتمام يتطلب دراسة متأنية. لذلك ، قررت أن أكتب ملاحظة صغيرة منفصلة حول هذا الموضوع. بالإضافة إلى ذلك ، أثناء تجربة رمز مشابه ، حددت عيبًا في PVS-Studio نفسه :). في بعض الحالات ، يكون صامتًا ، على الرغم من أنه يجب أن يصدر تحذيرات.
باختصار ، يعتمد ما إذا كان هذا الرمز يعمل أم لا على الترتيب الذي يتم تقييم الوسائط به. يعتمد ترتيب تقييم الوسائط على المترجم.
مكتبة PPrint
وصف موجز للمكتبة PPRINT :.
طابعة جميلة لـ C ++ الحديثة.
template <typename Container>
typename std::enable_if<......>::type print_internal(......) {
....
for (size_t i = 1; i < value.size() - 1; i++) {
print_internal(value[i], indent + indent_, "", level + 1);
if (is_container<T>::value == false)
print_internal_without_quotes(", ", 0, "\n");
else
print_internal_without_quotes(", ", 0, "\n");
}
....
}
تحذير PVS-Studio: V523 عبارة "then" تعادل عبارة "else". pprint.hpp 715 من
الغريب جدًا أن يتم تنفيذ نفس الإجراء بغض النظر عن الحالة. لا يوجد تعليق توضيحي خاص أيضًا. هذا كله مشابه جدًا لخطأ النسخ واللصق.
تحذيرات مماثلة:
- V523 عبارة "then" تعادل عبارة "else". pprint.hpp 780
- V523 عبارة "then" تعادل عبارة "else". pprint.hpp 851
- V523 عبارة "then" تعادل عبارة "else". pprint.hpp 927
- V523 عبارة "then" تعادل عبارة "else". pprint.hpp 1012
مكتبة Strf
وصف موجز لمكتبة Strf :
مكتبة تنسيق C ++ سريعة تدعم تحويل الترميز.الخطأ الأول
template <int Base>
class numpunct: private strf::digits_grouping
{
....
constexpr STRF_HD numpunct& operator=(const numpunct& other) noexcept
{
strf::digits_grouping::operator=(other);
decimal_point_ = other.decimal_point_;
thousands_sep_ = other.thousands_sep_;
}
....
};
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. numpunct.hpp 402
لقد نسينا كتابة "return * this؛" في نهاية الوظيفة.
ثاني خطأ مشابه
template <int Base>
class no_grouping final
{
constexpr STRF_HD no_grouping& operator=(const no_grouping& other) noexcept
{
decimal_point_ = other.decimal_point_;
}
....
}
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. numpunct.hpp 528.
مكتبة المؤشرات
وصف موجز لمكتبة المؤشرات :
مؤشرات النشاط لـ C ++ الحديثة.
static inline void move_up(int lines) { move(0, -lines); }
static inline void move_down(int lines) { move(0, -lines); } // <=
static inline void move_right(int cols) { move(cols, 0); }
static inline void move_left(int cols) { move(-cols, 0); }
تحذير PVS-Studio: V524 من الغريب أن جسم وظيفة "move_down" مكافئ تمامًا لجسم وظيفة "move_up". مؤشرات .hpp 983
لست متأكدًا مما إذا كان هذا خطأ. لكن الكود مريب للغاية. من المحتمل جدًا أنه تم نسخ وظيفة move_up وتغيير اسمها إلى move_down . لكنهم نسوا إزالة الطرح. يجدر التحقق من هذا الرمز.
ملحوظة. إذا كانت الشفرة صحيحة ، فأنت بحاجة إلى فهم أنها تضلل ليس فقط محللي الكود ، ولكن أيضًا مبرمجي الطرف الثالث الذين يرغبون في استخدام أو تطوير هذا الرمز. من المفيد إضافة تعليقات على هذا الرمز.
مكتبة مانيف
وصف موجز لمكتبة المانيف :
manif عبارة عن مكتبة نظرية C ++ 11 لا تحتوي على رأس فقط لتقدير الحالة التي تستهدف تطبيقات الروبوتات.
template <typename _Derived>
typename LieGroupBase<_Derived>::Scalar*
LieGroupBase<_Derived>::data()
{
return derived().coeffs().data();
}
template <typename _Derived>
const typename LieGroupBase<_Derived>::Scalar*
LieGroupBase<_Derived>::data() const
{
derived().coeffs().data(); // <=
}
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. lie_group_base.h 347
يتم تنفيذ الوظيفة غير الثابتة بشكل صحيح ، لكن الوظيفة الثابتة ليست كذلك. من المثير للاهتمام كيف حدث ذلك ...
مكتبة FakeIt
وصف موجز لمكتبة FakeIt :
FakeIt هو إطار عمل محاكاة بسيط لـ C ++. وهو يدعم GCC و Clang و MS Visual C ++. FakeIt مكتوب بلغة C ++ 11 ويمكن استخدامه لاختبار مشاريع C ++ 11 و C ++.
template<typename ... arglist>
struct ArgumentsMatcherInvocationMatcher :
public ActualInvocation<arglist...>::Matcher {
....
template<typename A>
void operator()(int index, A &actualArg) {
TypedMatcher<typename naked_type<A>::type> *matcher =
dynamic_cast<TypedMatcher<typename naked_type<A>::type> *>(
_matchers[index]);
if (_matching)
_matching = matcher->matches(actualArg);
}
....
const std::vector<Destructible *> _matchers;
};
تحذير PVS-Studio: V522 قد يكون هناك إلغاء مرجعية لمؤشر فارغ محتمل "المطابق". fakeit.hpp 6720 تتم تهيئة
مؤشر المطابق بالقيمة التي تم إرجاعها بواسطة عامل البث الديناميكي . ويمكن لهذا العامل إرجاع nullptr ، وهذا سيناريو محتمل للغاية. خلاف ذلك ، يكون استخدام static_cast بدلاً من dynamic_cast أكثر كفاءة . يوجد اشتباه بوجود خطأ مطبعي في الشرط وفي الحقيقة يجب كتابته:
if (matcher)
_matching = matcher->matches(actualArg);
مكتبة GuiLite
وصف موجز لمكتبة GuiLite :
أصغر مكتبة GUI ذات رأس فقط (4 KLOC) لجميع الأنظمة الأساسية.
#define CORRECT(x, high_limit, low_limit) {\
x = (x > high_limit) ? high_limit : x;\
x = (x < low_limit) ? low_limit : x;\
}while(0)
void refresh_wave(unsigned char frame)
{
....
CORRECT(y_min, m_wave_bottom, m_wave_top);
....
}
تحذير PVS-Studio: V529 فاصلة منقوطة فردية '؛' بعد عامل التشغيل "while". GuiLite.h 3413
لا يؤدي خطأ في ماكرو إلى بعض المشاكل. لكنه لا يزال خطأ ، لذلك قررت أن أصفه في المقالة.
كان من المخطط استخدام النمط الكلاسيكي do {...} بينما (....) في الماكرو . يسمح لك هذا بتنفيذ العديد من الإجراءات في كتلة واحدة وفي نفس الوقت يمكنك كتابة فاصلة منقوطة "؛" بعد الماكرو للجمال ، كما لو كان استدعاء دالة.
لكن في الماكرو المدروس ، نسوا بطريق الخطأ كتابة الكلمة الأساسية do . نتيجة لذلك ، فإن الماكرو ، كما هو ، مقسم إلى جزأين. الأول هو الكتلة. والثاني عبارة عن حلقة فارغة غير منفذة: while (0)؛ ...
وما هي المشكلة؟
على سبيل المثال ، لا يمكن استخدام مثل هذا الماكرو في بناء مثل:
if (A)
CORRECT(y_min, m_wave_bottom, m_wave_top);
else
Foo();
لن يتم ترجمة هذا الكود لأنه سيتم توسيعه إلى:
if (A)
{ ..... }
while(0);
else
Foo();
موافق ، من الأفضل إيجاد هذه المشكلة وإصلاحها في مرحلة تطوير المكتبة ، وليس في مرحلة استخدامها. تطبيق تحليل الكود الثابت :).

مكتبة PpluX
وصف موجز لمكتبة PpluX :
مكتبات C ++ ذات رأس واحد لجدولة مؤشر الترابط والتقديم وما إلى ذلك ...
struct DisplayList {
DisplayList& operator=(DisplayList &&d) {
data_ = d.data_;
d.data_ = nullptr;
}
....
}
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. صدقى
المكتبة العالمية
وصف موجز للمكتبة العالمية:
الهدف من الأعداد العالمية ، أو unums ، هو استبدال الفاصلة العائمة IEEE بنظام رقمي أكثر كفاءة واتساقًا رياضيًا في بيئات التنفيذ المتزامنة.الخطأ الأول
template<typename Scalar>
vector<Scalar> operator*(double scalar, const vector<Scalar>& v) {
vector<Scalar> scaledVector(v);
scaledVector *= scalar;
return v;
}
تحذير PVS-Studio: V1001 تم تعيين متغير "scaledVector" ولكن لا يتم استخدامه في نهاية الوظيفة. vector.hpp 124 مطبعي
. بدلاً من المتجه الأصلي v ، تحتاج الوظيفة إلى إرجاع متجه جديد ، متدرج الحجم .
يمكن رؤية خطأ مطبعي مشابه هنا: تم تعيين V1001 المتغير "normalizedVector" ولكن لا يتم استخدامه في نهاية الوظيفة. vector.hpp 131
الخطأ الثاني
template<typename Scalar>
class matrix {
....
matrix& diagonal() {
}
....
};
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. matrix.hpp 109
الخطأ الثالث
template<size_t fbits, size_t abits>
void module_subtract_BROKEN(
const value<fbits>& lhs, const value<fbits>& rhs, value<abits + 1>& result)
{
if (lhs.isinf() || rhs.isinf()) {
result.setinf();
return;
}
int lhs_scale = lhs.scale(),
rhs_scale = rhs.scale(),
scale_of_result = std::max(lhs_scale, rhs_scale);
// align the fractions
bitblock<abits> r1 =
lhs.template nshift<abits>(lhs_scale - scale_of_result + 3);
bitblock<abits> r2 =
rhs.template nshift<abits>(rhs_scale - scale_of_result + 3);
bool r1_sign = lhs.sign(), r2_sign = rhs.sign();
//bool signs_are_equal = r1_sign == r2_sign;
if (r1_sign) r1 = twos_complement(r1);
if (r1_sign) r2 = twos_complement(r2); // <=
....
}
تحذير PVS-Studio: V581 التعبيرات الشرطية لعبارات "if" الموجودة بجانب بعضها البعض متطابقة. فحص الأسطر: 789 ، 790. value.hpp 790
خطأ تقليدي ناتج عن Copy-Paste. أخذوا وضربوا الخط:
if (r1_sign) r1 = twos_complement(r1);
غيرنا r1 إلى r2 فيه :
if (r1_sign) r2 = twos_complement(r2);
ونسوا تغيير r1_sign . الخيار الصحيح:
if (r2_sign) r2 = twos_complement(r2);
مكتبات Chobo أحادية الرأس
وصف موجز لمكتبات Chobo أحادية الرأس :
مجموعة من مكتبات C ++ 11 صغيرة أحادية الرأس من Chobolabs.الخطأ الأول
template <typename T, typename U, typename Alloc = std::allocator<T>>
class vector_view
{
....
vector_view& operator=(vector_view&& other)
{
m_vector = std::move(other.m_vector);
}
....
}
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. vector_view.hpp 163
الخطأ الثاني
template <typename UAlloc>
vector_view& operator=(const std::vector<U, UAlloc>& other)
{
size_type n = other.size();
resize(n);
for (size_type i = 0; i < n; ++i)
{
this->at(i) = other[i];
}
}
تحذير PVS-Studio: يجب أن تقوم الدالة غير الفارغة V591 بإرجاع قيمة. vector_view.hpp 184
مكتبة PGM- الفهرس
وصف موجز لمكتبة فهرس PGM :
فهرس النماذج الهندسية المتقطعة (PGM-index) عبارة عن بنية بيانات تتيح البحث السريع ، والسلف ، والبحث عن النطاق والتحديثات في صفائف من مليارات العناصر باستخدام أوامر من حيث الحجم أقل من الفهارس التقليدية مع توفير نفس ضمانات وقت الاستعلام الأسوأ. ...الخطأ الأول
char* str_from_errno()
{
#ifdef MSVC_COMPILER
#pragma warning(disable:4996)
return strerror(errno);
#pragma warning(default:4996)
#else
return strerror(errno);
#endif
}
تحذير PVS-Studio: V665 من المحتمل أن يكون استخدام "تحذير # براغما (الافتراضي: X)" غير صحيح في هذا السياق. يجب استخدام "تحذير # براغما (دفع / فرقعة)" بدلاً من ذلك. فحص الأسطر: 9170 ، 9172. sdsl.hpp 9172 تحذير
غير صحيح بتعطيل المحول البرمجي مؤقتًا. مثل هذه الأخطاء يمكن التغاضي عنها إلى حد ما لكود المستخدم. لكن هذا بالتأكيد غير مسموح به في مكتبات الرأس فقط.
الخطأ الثاني
template<class t_int_vec>
t_int_vec rnd_positions(uint8_t log_s, uint64_t& mask,
uint64_t mod=0, uint64_t seed=17)
{
mask = (1<<log_s)-1; // <=
t_int_vec rands(1<<log_s ,0);
set_random_bits(rands, seed);
if (mod > 0) {
util::mod(rands, mod);
}
return rands;
}
تحذير PVS-Studio: V629 ضع في اعتبارك فحص التعبير '1 << log_s'. تحويل البت لقيمة 32 بت مع التوسيع اللاحق لنوع 64 بت. sdsl.hpp 1350
أحد الخيارات الصحيحة:
mask = ((uint64_t)(1)<<log_s)-1;
مكتبة Hnswlib
وصف موجز لمكتبة Hnswlib :
تنفيذ C ++ HNSW برأس فقط مع روابط بيثون. كود الورق لتجربة HNSW 200M SIFT.
template<typename dist_t>
class BruteforceSearch : public AlgorithmInterface<dist_t> {
public:
BruteforceSearch(SpaceInterface <dist_t> *s, size_t maxElements) {
maxelements_ = maxElements;
data_size_ = s->get_data_size();
fstdistfunc_ = s->get_dist_func();
dist_func_param_ = s->get_dist_func_param();
size_per_element_ = data_size_ + sizeof(labeltype);
data_ = (char *) malloc(maxElements * size_per_element_);
if (data_ == nullptr)
std::runtime_error(
"Not enough memory: BruteforceSearch failed to allocate data");
cur_element_count = 0;
}
....
}
تحذير PVS-Studio: V596 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون الكلمة الأساسية "رمي" مفقودة: throw runtime_error (FOO) ؛ bruteforce.h 26
نسيت أن تكتب بيان رمي قبل std :: runtime_error . خطأ آخر من هذا القبيل: V596 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون الكلمة الأساسية "رمي" مفقودة: throw runtime_error (FOO) ؛ 161
مكتبة dnn الصغيرة
وصف موجز لمكتبة tiny-dnn :
tiny-dnn هي تطبيق C ++ 14 للتعلم العميق. إنه مناسب للتعلم العميق على موارد حسابية محدودة وأنظمة مضمنة وأجهزة إنترنت الأشياء.الخطأ الأول
class nn_error : public std::exception {
public:
explicit nn_error(const std::string &msg) : msg_(msg) {}
const char *what() const throw() override { return msg_.c_str(); }
private:
std::string msg_;
};
inline Device::Device(device_t type, const int platform_id, const int device_id)
: type_(type),
has_clcuda_api_(true),
platform_id_(platform_id),
device_id_(device_id) {
....
#else
nn_error("TinyDNN has not been compiled with OpenCL or CUDA support.");
#endif
}
تحذير PVS-Studio: V596 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون الكلمة الأساسية "رمي" مفقودة: throw nn_error (FOO)؛ device.h 68
nn_error ليست وظيفة تؤدي إلى استثناء ، ولكنها مجرد فئة. لذلك من الصحيح استخدامه على النحو التالي:
throw nn_error("TinyDNN has not been compiled with OpenCL or CUDA support.");
إساءة استخدام أخرى لهذه الفئة: V596 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون الكلمة الأساسية "رمي" مفقودة: throw nn_error (FOO)؛ conv2d_op_opencl.h 136
الخطأ الثاني
inline std::string format_str(const char *fmt, ...) {
static char buf[2048];
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
#ifdef _MSC_VER
#pragma warning(default : 4996)
#endif
return std::string(buf);
}
تحذير PVS-Studio: V665 من المحتمل أن يكون استخدام "تحذير # براغما (الافتراضي: X)" غير صحيح في هذا السياق. يجب استخدام "تحذير # براغما (دفع / فرقعة)" بدلاً من ذلك. تحقق من السطور: 139 ، 146. util.h 146
مكتبة Dlib
وصف موجز لمكتبة Dlib :
Dlib عبارة عن مجموعة أدوات C ++ حديثة تحتوي على خوارزميات وأدوات التعلم الآلي لإنشاء برامج معقدة في C ++ لحل مشاكل العالم الحقيقي.الخطأ الأول
للتسلية ، حاول أن تجد هذا الخطأ بنفسك.
class bdf_parser
{
public:
enum bdf_enums
{
NO_KEYWORD = 0,
STARTFONT = 1,
FONTBOUNDINGBOX = 2,
DWIDTH = 4,
DEFAULT_CHAR = 8,
CHARS = 16,
STARTCHAR = 32,
ENCODING = 64,
BBX = 128,
BITMAP = 256,
ENDCHAR = 512,
ENDFONT = 1024
};
....
bool parse_header( header_info& info )
{
....
while ( 1 )
{
res = find_keywords( find | stop );
if ( res & FONTBOUNDINGBOX )
{
in_ >> info.FBBx >> info.FBBy >> info.Xoff >> info.Yoff;
if ( in_.fail() )
return false; // parse_error
find &= ~FONTBOUNDINGBOX;
continue;
}
if ( res & DWIDTH )
{
in_ >> info.dwx0 >> info.dwy0;
if ( in_.fail() )
return false; // parse_error
find &= ~DWIDTH;
info.has_global_dw = true;
continue;
}
if ( res & DEFAULT_CHAR )
{
in_ >> info.default_char;
if ( in_.fail() )
return false; // parse_error
find &= ~DEFAULT_CHAR;
continue;
}
if ( res & NO_KEYWORD )
return false; // parse_error: unexpected EOF
break;
}
....
};
وجدت ذلك؟

هي هنا:
if ( res & NO_KEYWORD )
تحذير PVS-Studio: V616 يتم استخدام الثابت "NO_KEYWORD" المسمى بقيمة 0 في عملية البت. fonts.cpp 288
الثابت المسمى NO_KEYWORD له القيمة 0. لذلك ، الشرط لا معنى له. سيكون من الصحيح أن تكتب:
if ( res == NO_KEYWORD )
تم العثور على فحص آخر غير صحيح هنا: V616 يتم استخدام "NO_KEYWORD" المسمى الثابت بقيمة 0 في عملية البت. fonts.cpp 334
الخطأ الثاني
void set(std::vector<tensor*> items)
{
....
epa.emplace_back(new enable_peer_access(*g[0], *g[i]));
....
}
تحذير PVS-Studio: V1023 تتم إضافة مؤشر بدون مالك إلى حاوية "epa" بواسطة طريقة "emplace_back". يحدث تسرب للذاكرة في حالة حدوث استثناء. tensor_tools.h 1665
لفهم مكان الصيد ، أقترح التعرف على وثائق التشخيص V1023 .
الخطأ الثالث
template <
typename detection_type,
typename label_type
>
bool is_track_association_problem (
const std::vector<
std::vector<labeled_detection<detection_type,label_type> > >& samples
)
{
if (samples.size() == 0)
return false;
unsigned long num_nonzero_elements = 0;
for (unsigned long i = 0; i < samples.size(); ++i)
{
if (samples.size() > 0)
++num_nonzero_elements;
}
if (num_nonzero_elements < 2)
return false;
....
}
تحذير PVS-Studio: V547 Expression 'sample.size ()> 0' صحيح دائمًا. svm.h 360
هذا رمز غريب جدًا جدًا! إذا بدأت حلقة ، فإن الشرط (sample.size ()> 0) يكون دائمًا صحيحًا. لذلك ، يمكن تبسيط الحلقة:
for (unsigned long i = 0; i < samples.size(); ++i)
{
++num_nonzero_elements;
}
بعد ذلك ، يتضح أن الحلقة ليست ضرورية على الإطلاق. يمكن كتابتها بشكل أسهل وأكثر كفاءة:
unsigned long num_nonzero_elements = samples.size();
ولكن هل كان مخططًا للقيام به؟ من الواضح أن الكود يستحق دراسة متأنية من قبل مبرمج.
الخطأ الرابع
class console_progress_indicator
{
....
double seen_first_val;
....
};
bool console_progress_indicator::print_status (
double cur, bool always_print)
{
....
if (!seen_first_val)
{
start_time = cur_time;
last_time = cur_time;
first_val = cur;
seen_first_val = true; // <=
return false;
}
....
}
تحذير PVS-Studio: V601 يتم تحويل نوع bool ضمنيًا إلى النوع المزدوج. console_progress_indicator.h 136 يتم كتابة القيمة true
لعضو من فئة النوع double . حسنًا ... الخطأ الخامس
void file::init(const std::string& name)
{
....
WIN32_FIND_DATAA data;
HANDLE ffind = FindFirstFileA(state.full_name.c_str(), &data);
if (ffind == INVALID_HANDLE_VALUE ||
(data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) != 0)
{
throw file_not_found("Unable to find file " + name);
}
else
{
....
}
}
تحذير PVS-Studio: V773 تم طرح الاستثناء بدون إغلاق الملف المشار إليه بواسطة مؤشر "ffind". تسرب الموارد ممكن. dir_nav_kernel_1.cpp 60
إذا تم العثور على دليل ، يتم طرح استثناء. لكن من سيغلق المقبض؟
الخطأ السادس
مكان آخر غريب جدا.
inline double poly_min_extrap(double f0, double d0,
double x1, double f_x1,
double x2, double f_x2)
{
....
matrix<double,2,2> m;
matrix<double,2,1> v;
const double aa2 = x2*x2;
const double aa1 = x1*x1;
m = aa2, -aa1,
-aa2*x2, aa1*x1;
v = f_x1 - f0 - d0*x1,
f_x2 - f0 - d0*x2;
....
}
تحذير PVS-Studio: V521 مثل هذه التعبيرات باستخدام عامل التشغيل "،" تعتبر خطيرة. تأكد من صحة التعبير. optimization_line_search.h 211
مجدول لتهيئة المصفوفات. لكن كل هذه aa2 و f_x1 و d0 وما إلى ذلك هي مجرد متغيرات من النوع المزدوج . هذا يعني أن الفواصل لا تفصل بين الوسيطات التي تهدف إلى إنشاء مصفوفات ، ولكنها عوامل فاصلة عادية تُرجع قيمة الجانب الأيمن.
خاتمة
في بداية المقال ، قدمت مثالًا على كيفية دمج العديد من الأشياء المفيدة في وقت واحد. يعد استخدام محلل ثابت مفيدًا أيضًا في نفس الوقت لعدة أسباب:
- تدريب. من خلال دراسة تحذيرات المحلل ، يمكنك تعلم الكثير من الأشياء الجديدة والمفيدة. أمثلة: memset ، #pragma warning ، emplace_back ، محاذاة بدقة .
- الاكتشاف المبكر للأخطاء المطبعية والأخطاء ونقاط الضعف المحتملة.
- أصبحت الشفرة تدريجياً أفضل وأبسط وأكثر قابلية للفهم.
- يمكنك أن تفخر وتخبر الجميع أنك تستخدم التقنيات الحديثة عند تطوير المشاريع :). وهذه دعابة جزئية فقط. هذه ميزة تنافسية حقيقية.
السؤال الوحيد هو كيف نبدأ وكيف ننفذها بدون ألم وكيف نستخدمها بشكل صحيح؟ ستساعدك المقالات التالية في هذا:
- كيف يمكنك عرض التحذيرات الشيقة التي تم إنشاؤها بواسطة محلل PVS-Studio لرمز C و C ++ بسرعة؟
- كيفية تنفيذ محلل كود ثابت في مشروع قديم وعدم تثبيط الفريق .
- أدخل التحليل الثابت في العملية ، بدلاً من البحث عن الأخطاء بها .

إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Andrey Karpov. التحقق من مجموعة مكتبة C ++ ذات رأس فقط (awesome-hpp) .