ومرة أخرى إلى الفضاء: كيف زار Stellarium وحيد القرن

خلال فترة وجودهم بأكملها ، بذل الناس قدراً هائلاً من الجهد لدراسة المنطقة بأكملها تقريباً من السماء المرصعة بالنجوم. لقد درسنا حتى الآن مئات الآلاف من الكويكبات والمذنبات والسدم والنجوم والمجرات والكواكب. لكي ترى كل هذا الجمال بنفسك ، ليس من الضروري مغادرة المنزل وشراء التلسكوب. يمكنك تثبيت Stellarium - قبة فلكية افتراضية على جهاز الكمبيوتر الخاص بك ، والنظر إلى سماء الليل ، ملقاة بشكل مريح على الأريكة ... ولكن هل هذا مريح؟ لمعرفة إجابة هذا السؤال ، سوف نتحقق من Stellarium بحثًا عن أخطاء في رمز الكمبيوتر.



قليلا عن المشروع ...


وفقًا للوصف الموجود على موقع Wikipedia على الويب ، فإن Stellarium عبارة عن قبة سلكية افتراضية مفتوحة المصدر متاحة لأنظمة Linux و Mac OS X و Microsoft Windows و Symbian و Android و iOS ، وكذلك MeeGo. يستخدم البرنامج تقنيات OpenGL و Qt لإنشاء سماء واقعية في الوقت الفعلي. مع Stellarium ، يمكنك رؤية ما يمكنك رؤيته من خلال تلسكوب متوسط ​​وحتى كبير. يوفر البرنامج أيضًا ملاحظات حول كسوف الشمس وحركة المذنبات.

تم إنشاء Stellarium بواسطة المبرمج الفرنسي فابيان شيرو ، الذي أطلق المشروع في صيف عام 2001. ومن بين المطورين البارزين الآخرين روبرت سبيرمان ، يوهانس جادزويك ، ماثيو جيتس ، تيموثي ريفز ، بوجدان مارينوف ويوهان ميريس ، المسؤول عن العمل الفني.

... وحول محلل


تم تنفيذ تحليل المشروع باستخدام محلل الشفرات الثابتة PVS-Studio. هذه أداة لاكتشاف الأخطاء ونقاط الضعف المحتملة في التعليمات البرمجية المصدر للبرامج المكتوبة بلغات C و C ++ و C # (قريباً في Java!). إنه يعمل على Windows و Linux و macOS. وهي مصممة لأولئك الذين يحتاجون إلى تحسين جودة التعليمات البرمجية الخاصة بهم.

كان التحليل بسيط جدا. أولاً ، قمت بتنزيل مشروع Stellarium من GitHub ، ثم قمت بتثبيت جميع الحزم اللازمة للتجميع. نظرًا لأن المشروع مبني باستخدام Qt Creator ، فقد استخدمت نظام تتبع إطلاق برنامج التحويل البرمجي ، والذي تم تضمينه في الإصدار المستقل للمحلل. هناك يمكنك عرض تقرير التحليل النهائي.

ربما تساءل القراء الجدد ومستخدمي Stellarium : لماذا يظهر يونيكورن في عنوان المقال وكيف يرتبط بتحليل الكود؟ أجيب: أنا واحد من مطوري PVS-Studio ، واليونيكورن هو التميمة الخبيثة المفضلة لدينا. حتى يصل!


آمل أنه بفضل هذا المقال ، سيتعلم القراء شيئًا جديدًا لأنفسهم ، وسيتمكن مطورو Stellarium من التخلص من بعض الأخطاء وتحسين جودة الشفرة.

اصنعي القهوة بالكرواسان واجعل نفسك مرتاحًا ، لأننا سنذهب إلى الجزء الأكثر إثارة للاهتمام - نظرة عامة على نتائج تحليل الأخطاء وتحليلها!

الظروف المشبوهة


من أجل مزيد من المتعة في القراءة ، أقترح عدم النظر مباشرةً في تحذير المحلل ، لكنني أحاول هنا والمزيد من البحث عن الأخطاء بنفسك.

void QZipReaderPrivate::scanFiles() { .... // find EndOfDirectory header int i = 0; int start_of_directory = -1; EndOfDirectory eod; while (start_of_directory == -1) { const int pos = device->size() - int(sizeof(EndOfDirectory)) - i; if (pos < 0 || i > 65535) { qWarning() << "QZip: EndOfDirectory not found"; return; } device->seek(pos); device->read((char *)&eod, sizeof(EndOfDirectory)); if (readUInt(eod.signature) == 0x06054b50) break; ++i; } .... } 

تحذير PVS-Studio: V654 شرط "start_of_directory == - 1" للحلقة صحيح دائمًا. qzip.cpp 617

هل يمكن أن تجد خطأ؟ إذا كان الأمر كذلك ، ثم الثناء.

الخطأ يكمن في حالة حلقة في حين . هذا صحيح دائمًا ، حيث لا يتغير start_of_directory المتغير في نص الحلقة. على الأرجح ، لن تكون الدورة أبدية ، لأنها تحتوي على عائد وكسر ، لكن هذا الرمز يبدو غريباً.

يبدو لي أنه في الشفرة نسوا إجراء الواجب start_of_directory = pos في المكان الذي يتم فيه التحقق من التوقيع. ثم بيان استراحة ربما لا لزوم لها. في هذه الحالة ، يمكن إعادة كتابة الكود كما يلي:

 int i = 0; int start_of_directory = -1; EndOfDirectory eod; while (start_of_directory == -1) { const int pos = device->size() - int(sizeof(EndOfDirectory)) - i; if (pos < 0 || i > 65535) { qWarning() << "QZip: EndOfDirectory not found"; return; } device->seek(pos); device->read((char *)&eod, sizeof(EndOfDirectory)); if (readUInt(eod.signature) == 0x06054b50) start_of_directory = pos; ++i; } 

ومع ذلك ، لست متأكدًا مما يجب أن يكون عليه الرمز. من الأفضل أن يقوم مطورو المشروع أنفسهم بتحليل هذا الجزء من البرنامج وإجراء التغييرات اللازمة.

حالة غريبة أخرى:

 class StelProjectorCylinder : public StelProjector { public: .... protected: .... virtual bool intersectViewportDiscontinuityInternal(const Vec3d& capN, double capD) const { static const SphericalCap cap1(1,0,0); static const SphericalCap cap2(-1,0,0); static const SphericalCap cap3(0,0,-1); SphericalCap cap(capN, capD); return cap.intersects(cap1) && cap.intersects(cap2) && cap.intersects(cap2); } }; 

تحذير PVS-Studio: V501 هناك تعبيرات شبه متطابقة 'cap.intersects (cap2)' إلى اليسار وإلى يمين المشغل '&&'. StelProjectorClasses.hpp 175

كما قد تكون خمنت بالفعل ، يكمن الخطأ في السطر الأخير من الوظيفة: لقد قام المبرمج بخطأ مطبعي ، وفي النهاية تبين أن الدالة تقوم بإرجاع النتيجة بغض النظر عن قيمة cap3 .

هذا النوع من الأخطاء شائع للغاية: في كل مشروع تم اختباره تقريبًا ، واجهنا الأخطاء المطبعية المتعلقة بأسماء اسم النموذج 1 والاسم 2 وما شابه. عادة ، ترتبط مثل هذه الأخطاء بنسخ اللصق.

هذا المثيل الرمزي هو مثال رئيسي لنمط خطأ شائع آخر ، والذي أجرينا له حتى دراسة مصغرة منفصلة. وصفه زميلي أندريه كاربوف بأنه " تأثير الخط الأخير ". إذا لم تكن معتادًا على هذه المادة ، أقترح فتح علامة تبويب في المتصفح للقراءة لاحقًا ، ولكن الآن ، فلنواصل.

 void BottomStelBar::updateText(bool updatePos) { .... updatePos = true; .... if (location->text() != newLocation || updatePos) { updatePos = true; .... } .... if (fov->text() != str) { updatePos = true; .... } .... if (fps->text() != str) { updatePos = true; .... } if (updatePos) { .... } } 

تحذيرات PVS-Studio:

  • V560 جزء من التعبير الشرطي صحيح دائمًا: updatePos. StelGuiItems.cpp 732
  • V547 Expression 'updatePos' صحيح دائمًا. StelGuiItems.cpp 831
  • تتم إعادة كتابة المعلمة V763 "updatePos" دائمًا في هيكل الوظيفة قبل استخدامها. StelGuiItems.cpp 690

يتم دائمًا الكتابة فوق قيمة معلمة updatePos قبل استخدامها ، أي ستعمل الوظيفة كما هي بغض النظر عن القيمة التي تم تمريرها إليها.

تبدو غريبة ، أليس كذلك؟ في جميع الأماكن التي تكون فيها المعلمة updatePos ، صحيح . هذا يعني أن الشروط (الموقع-> text ()! = NewLocation || updatePos) وإذا كان (updatePos) سيكون دائمًا صحيحًا.

مقتطف آخر:

 void LandscapeMgr::onTargetLocationChanged(StelLocation loc) { .... if (pl && flagEnvironmentAutoEnabling) { QSettings* conf = StelApp::getInstance().getSettings(); setFlagAtmosphere(pl->hasAtmosphere() & conf->value("landscape/flag_atmosphere", true).toBool()); setFlagFog(pl->hasAtmosphere() & conf->value("landscape/flag_fog", true).toBool()); setFlagLandscape(true); } .... } 

تحذيرات PVS-Studio:

  • V792 سيتم استدعاء وظيفة "toBool" الموجودة على يمين المشغل "&" بغض النظر عن قيمة المعامل الأيسر. ربما ، من الأفضل استخدام "&&". LandscapeMgr.cpp 782
  • V792 سيتم استدعاء وظيفة "toBool" الموجودة على يمين المشغل "&" بغض النظر عن قيمة المعامل الأيسر. ربما ، من الأفضل استخدام "&&". LandscapeMgr.cpp 783

اكتشف المحلل تعبيرًا مشبوهًا في الوسيطتين لوظائف setFlagAtmosphere و setFlagFog . في الواقع: على كلا الجانبين من المشغل بت وهناك قيم نوع منطقي . بدلاً من عامل التشغيل & ، يجب عليك استخدام عامل التشغيل && ، وسأشرح لك الآن السبب.

نعم ، ستكون نتيجة هذا التعبير صحيحة دائمًا. قبل استخدام bitwise "و" ، سيتم ترقية كلا المعاملين إلى int . في C ++ ، مثل هذا التحويل لا لبس فيه : يتم تحويل false إلى 0 ، ويتم تحويل true إلى 1. لذلك ، ستكون نتيجة هذا التعبير هي نفسها كما لو تم استخدام عامل التشغيل && .

ولكن هناك فارق بسيط. عند حساب نتيجة العملية && ، يتم استخدام "الحساب الكسول". إذا كانت قيمة المعامل الأيسر غير صحيحة ، فلن يتم احتساب القيمة الصحيحة ، لأن المنطقي "و" في أي حال سيعودان خطأ . يتم ذلك لتوفير موارد الحوسبة ويسمح لك بكتابة تصاميم أكثر تعقيدًا. على سبيل المثال ، يمكنك التحقق من أن المؤشر غير فارغ ، وإذا كان الأمر كذلك ، فقم بإلغاء تحديده لإجراء فحص إضافي. مثال: if (ptr && ptr-> foo ()) .

لا يتم تنفيذ هذا "الحساب الكسول" عند استخدام عامل التشغيل bitwise & : تعبيرات conf-> سيتم تقييم القيمة ("..." ، true) .toBool () في كل مرة ، بغض النظر عن القيمة pl-> hasAtmosphere () .

في حالات نادرة ، يتم ذلك عن قصد. على سبيل المثال ، إذا كان لحساب المعامل الأيمن "تأثيرات جانبية" ، فسيتم استخدام النتيجة لاحقًا. ليس من الجيد القيام بذلك أيضًا ، لأنه يعقد فهم الكود ويعقد صيانته. بالإضافة إلى ذلك ، لم يتم تحديد ترتيب حساب المعاملات & ، لذلك في بعض الحالات باستخدام مثل هذه "الحيل" ، يمكنك الحصول على سلوك غير محدد.

إذا كنت بحاجة إلى حفظ الآثار الجانبية - فقم بذلك في سطر منفصل واحفظ النتيجة في متغير منفصل. سيكون الأشخاص الذين سيعملون مع هذا الرمز في المستقبل ممتنين لك :)


ننتقل إلى الموضوع التالي.

معالجة الذاكرة غير صحيحة


لنبدأ موضوع الذاكرة الديناميكية مع هذا الجزء:

 /************ Basic Edge Operations ****************/ /* __gl_meshMakeEdge creates one edge, * two vertices, and a loop (face). * The loop consists of the two new half-edges. */ GLUEShalfEdge* __gl_meshMakeEdge(GLUESmesh* mesh) { GLUESvertex* newVertex1 = allocVertex(); GLUESvertex* newVertex2 = allocVertex(); GLUESface* newFace = allocFace(); GLUEShalfEdge* e; /* if any one is null then all get freed */ if ( newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) { if (newVertex1 != NULL) { memFree(newVertex1); } if (newVertex2 != NULL) { memFree(newVertex2); } if (newFace != NULL) { memFree(newFace); } return NULL; } e = MakeEdge(&mesh->eHead); if (e == NULL) { return NULL; } MakeVertex(newVertex1, e, &mesh->vHead); MakeVertex(newVertex2, e->Sym, &mesh->vHead); MakeFace(newFace, e, &mesh->fHead); return e; } 

تحذيرات PVS-Studio:

  • V773 تم إنهاء الوظيفة دون تحرير مؤشر 'newVertex1'. تسرب الذاكرة ممكن. mesh.c 312
  • V773 تم إنهاء الوظيفة دون تحرير مؤشر "newVertex2". تسرب الذاكرة ممكن. mesh.c 312
  • V773 تم إنهاء الوظيفة دون تحرير مؤشر "newFace". تسرب الذاكرة ممكن. mesh.c 312

تقوم الوظيفة بتخصيص ذاكرة للعديد من الهياكل وتمريرها إلى المؤشرات newVertex1 ، newVertex2 (أسماء مثيرة للاهتمام ، أليس كذلك؟) و newFace . إذا تبين أن أحدها صفري ، فسيتم تحرير كل الذاكرة المحجوزة داخل الوظيفة ، وبعد ذلك يترك تدفق التحكم الوظيفة.

ماذا يحدث إذا تم تخصيص الذاكرة للهياكل الثلاثة بشكل صحيح وإرجاع الدالة MakeEdge (& mesh-> eHead) NULL ؟ سوف تدفق السيطرة تصل إلى عودة الثانية.

بما أن المؤشرات newVertex1 و newVertex2 و newFace هي متغيرات محلية ، فستتوقف عن الوجود بعد الخروج من الوظيفة. لكن إطلاق الذاكرة التي تخصهم لن يحدث. ستبقى محفوظة ، ولكن لن نتمكن من الوصول إليها بعد الآن.

وتسمى هذه الحالات تسرب الذاكرة. سيناريو نموذجي مع مثل هذا الخطأ: مع الاستخدام المطول للبرنامج ، يبدأ في استهلاك المزيد والمزيد من ذاكرة الوصول العشوائي ، حتى استنفادها.

تجدر الإشارة إلى أنه في هذا المثال ، العائد الثالث ليس خطأ. تقوم دالتا MakeVertex و MakeFace بنقل عناوين الذاكرة المخصصة إلى هياكل البيانات الأخرى ، وبالتالي تفويض المسؤولية عن إصدارها.

الخلل التالي هو في الطريقة ، والتي تأخذ 90 خطوط. للراحة ، قمت بتخفيضها ، ولم يتبق سوى مناطق المشكلة.

 void AstroCalcDialog::drawAngularDistanceGraph() { .... QVector<double> xs, ys; .... } 

سطر واحد فقط اليسار. دعني أعطيك تلميحًا: هذا هو الإشارة الوحيدة لكائنات xs و ys .

تحذيرات PVS-Studio:

  • تم إنشاء كائن V808 'xs' من النوع 'QVector' ولكن لم يتم استخدامه. AstroCalcDialog.cpp 5329
  • تم إنشاء كائن V808 'ys' من النوع 'QVector' ولكن لم يتم استخدامه. AstroCalcDialog.cpp 5329

يتم إنشاء المتجهات xs و ys ، لكن لا يتم استخدامها في أي مكان. اتضح أنه في كل مرة تستخدم فيها طريقة drawAngularDistanceGraph ، يحدث إنشاء إضافي وحذف حاوية فارغة. أعتقد أن هذا الإعلان ظل في الكود بعد إعادة بيعه. هذا ، بالطبع ، ليس خطأ ، لكن يجب عليك إزالة الكود الإضافي.

يلقي نوع غريب


مثال آخر بعد قليل من التنسيق يشبه هذا:

 void SatellitesDialog::updateSatelliteData() { .... // set default buttonColor = QColor(0.4, 0.4, 0.4); .... } 

لفهم ماهية الخطأ ، سيتعين عليك إلقاء نظرة على النماذج الأولية لمنشئي فئة Qcolor :


تحذيرات PVS-Studio:

  • V674 يتم الحرف "0.4" من النوع "المزدوج" يتم ضمنيًا إلى كتابة "int" أثناء استدعاء وظيفة "QColor". تفقد الحجة الأولى. SatellitesDialog.cpp 413
  • V674 يتم الحرف "0.4" من النوع "المزدوج" يتم ضمنيًا إلى كتابة "int" أثناء استدعاء وظيفة "QColor". تفقد الحجة الثانية. SatellitesDialog.cpp 413
  • V674 يتم الحرف "0.4" من النوع "المزدوج" يتم ضمنيًا إلى كتابة "int" أثناء استدعاء وظيفة "QColor". تفقد الحجة الثالثة. SatellitesDialog.cpp 413

لا تحتوي فئة Qcolor على مُنشآت تقبل النوع المزدوج ، لذلك سيتم تحويل الوسائط في المثال ضمنيًا إلى int . يؤدي هذا إلى أن يكون للحقول r و g و b للكائن buttonColor قيمة 0 .

إذا كان المبرمج يهدف إلى إنشاء كائن من قيم type double ، فيجب عليه استخدام مُنشئ مختلف.

على سبيل المثال ، يمكنك استخدام مُنشئ يقبل Qrgb بكتابة:

 buttonColor = QColor(QColor::fromRgbF(0.4, 0.4, 0.4)); 

كان يمكن القيام به بشكل مختلف. يستخدم Qt القيم الحقيقية في النطاق [0.0 ، 1.0] أو قيم عدد صحيح في النطاق [0 ، 255] للإشارة إلى ألوان RGB.

لذلك ، يمكن للمبرمج ترجمة القيم من الحقيقي إلى عدد صحيح عن طريق كتابة مثل هذا:

 buttonColor = QColor((int)(255 * 0.4), (int)(255 * 0.4), (int)(255 * 0.4)); 

او فقط

 buttonColor = QColor(102, 102, 102); 

هل تشعر بالملل؟ لا تقلق: هناك أخطاء أكثر إثارة أمامنا.


"يونيكورن في الفضاء." عرض من Stellarium.

أخطاء أخرى


في النهاية ، تركت لك المزيد من اللذيذ :) هيا بنا إلى واحد منهم.

 HipsTile* HipsSurvey::getTile(int order, int pix) { .... if (order == orderMin && !allsky.isNull()) { int nbw = sqrt(12 * 1 << (2 * order)); int x = (pix % nbw) * allsky.width() / nbw; int y = (pix / nbw) * allsky.width() / nbw; int s = allsky.width() / nbw; QImage image = allsky.copy(x, y, s, s); .... } .... } 

تحذير PVS-Studio: V634 أولوية العملية "*" أعلى من العملية "<<". من الممكن استخدام الأقواس في التعبير. StelHips.cpp 271

حسنًا ، هل تمكنت من اكتشاف الخطأ؟ النظر في التعبير (12 * 1 << (2 * ترتيب)) بمزيد من التفاصيل. يشير المحلل إلى أن العملية ' * ' لها أولوية أعلى من عملية تحول البت ' << '. من السهل أن نرى أن ضرب 12 في 1 لا معنى له ، وأن الأقواس حول 2 * ليست مطلوبة.

  ,    : int nbw = sqrt(12 * (1 << 2 * order));     <i>12 </i>     . 

ملاحظة بالإضافة إلى ذلك ، أود أن أشير إلى أنه إذا كانت قيمة المعامل الأيمن " << " أكبر من أو تساوي عدد وحدات البت في المعامل الأيسر ، فلن يتم تحديد النتيجة. نظرًا لأن القيم الحرفية الرقمية هي int افتراضيًا ، والتي تستغرق 32 بت ، يجب ألا تتجاوز قيمة معلمة الطلب 15 . خلاف ذلك ، قد يؤدي تقييم التعبير إلى سلوك غير محدد.

نواصل. الطريقة أدناه مربكة للغاية ، لكنني متأكد من أن قارئًا متطورًا سيتعامل مع اكتشاف الخطأ :)

 /* inherits documentation from base class */ QCPRange QCPStatisticalBox:: getKeyRange(bool& foundRange, SignDomain inSignDomain) const { foundRange = true; if (inSignDomain == sdBoth) { return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); } else if (inSignDomain == sdNegative) { if (mKey + mWidth * 0.5 < 0) return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); else if (mKey < 0) return QCPRange(mKey - mWidth * 0.5, mKey); else { foundRange = false; return QCPRange(); } } else if (inSignDomain == sdPositive) { if (mKey - mWidth * 0.5 > 0) return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); else if (mKey > 0) return QCPRange(mKey, mKey + mWidth * 0.5); else { foundRange = false; return QCPRange(); } } foundRange = false; return QCPRange(); } 

تحذير PVS-Studio: تم اكتشاف رمز غير قابل للوصول V779 . من الممكن وجود خطأ. qcustomplot.cpp 19512.

والحقيقة هي أن كل شيء إذا ... فروع أخرى لها عودة . لذلك ، لا يصل تدفق التحكم أبدًا إلى آخر سطرين.

بشكل عام ، سيتم تشغيل هذا المثال بشكل طبيعي ويعمل بشكل صحيح. ولكن وجود رمز لا يمكن الوصول إليه وحده هو إشارة. في هذه الحالة ، يشير إلى البنية غير الصحيحة للطريقة ، مما يؤدي إلى تعقيد قابلية قراءة التعليمات البرمجية وفهمها إلى حد كبير.

يجب إعادة تشكيل جزء الكود هذا ، والحصول على وظيفة أكثر دقة في الإخراج. على سبيل المثال ، مثل هذا:

 /* inherits documentation from base class */ QCPRange QCPStatisticalBox:: getKeyRange(bool& foundRange, SignDomain inSignDomain) const { foundRange = true; switch (inSignDomain) { case sdBoth: { return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); break; } case sdNegative: { if (mKey + mWidth * 0.5 < 0) return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); else if (mKey < 0) return QCPRange(mKey - mWidth * 0.5, mKey); break; } case sdPositive: { if (mKey - mWidth * 0.5 > 0) return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5); else if (mKey > 0) return QCPRange(mKey, mKey + mWidth * 0.5); break; } } foundRange = false; return QCPRange(); } 

سيكون آخر تقييم في الخطأ هو الخطأ الذي أعجبني أكثر. رمز بقعة المتاعب قصير وبسيط:

 Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3) : distance(0.0f), sDistance(0.0f) { Plane(v1, v2, v3, SPolygon::CCW); } 

هل لاحظت شيئًا مشبوهًا؟ لا يمكن للجميع :)

تحذير PVS-Studio: V603 تم إنشاء الكائن ولكن لا يتم استخدامه. إذا كنت ترغب في استدعاء المنشئ ، فيجب استخدام "هذا-> Plane :: Plane (....)". Plane.cpp 29

توقع المبرمج أن تتم تهيئة بعض حقول الكائن داخل المُنشئ المتداخل ، لكن اتضح أنه: عندما يتم استدعاء مُنشئ Plane (Vec3f & v1 ، Vec3f & v2 ، Vec3f & v3) ، يتم إنشاء كائن مؤقت غير مسمى بداخله ، يتم حذفه على الفور. نتيجة لذلك ، يبقى جزء من الكائن غير مهيأ.

لكي يعمل الرمز بشكل صحيح ، يجب عليك استخدام الميزة المريحة والآمنة لـ C ++ 11 - مُنشئ التفويض:

 Plane::Plane(Vec3f& v1, Vec3f& v2, Vec3f& v3) : Plane(v1, v2, v3, SPolygon::CCW) { distance = 0.0f; sDistance = 0.0f; } 

ولكن إذا كنت تستخدم المحول البرمجي للإصدارات الأقدم من اللغة ، فيمكنك الكتابة هكذا:

 Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3) : distance(0.0f), sDistance(0.0f) { this->Plane::Plane(v1, v2, v3, SPolygon::CCW); } 

أو هكذا:

 Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3) : distance(0.0f), sDistance(0.0f) { new (this) Plane(v1, v2, v3, SPolygon::CCW); } 

ألاحظ أن الطريقتين الأخيرتين خطيرتان للغاية . لذلك ، يجب أن تكون حذراً للغاية وأن تفهم جيدًا كيف تعمل هذه الطرق.

الخاتمة


ما هي الاستنتاجات التي يمكن إجراؤها حول جودة كود ستيلاريوم؟ بصراحة ، لم يكن هناك الكثير من الأخطاء. أيضًا ، في المشروع بالكامل ، لم أجد خطأً واحدًا يرتبط فيه الكود بسلوك غير محدد. فيما يتعلق بمشروع المصادر المفتوحة ، تحولت جودة الشفرة إلى مستوى عالٍ ، حيث قمت بإقلاع قبعتي للمطورين. يا رفاق رائعة! كنت مسروراً ومهتماً بمراجعة مشروعك.

ماذا عن القبة السماوية نفسها - أنا استخدامها في كثير من الأحيان. لسوء الحظ ، أعيش في مدينة ، نادراً ما يمكنني الاستمتاع بسماء صافية في الليل ، ويتيح لي ستيلاريوم أن أكون في أي مكان في العالم دون أن أستيقظ من الأريكة. انها مريحة حقا!

أنا أحب بشكل خاص وضع "كوكبة الفن". مشهد الشخصيات الضخمة التي تغطي السماء بأكملها في رقصة غريبة هو لالتقاط الأنفاس.


"رقصة غريبة." عرض من Stellarium.

يميل أبناء الأرض إلى ارتكاب الأخطاء ، ولا يوجد شيء مخزٍ في حقيقة أن هذه الأخطاء تتسرب إلى الشفرة. لهذا الغرض ، تم تطوير أدوات تحليل الشفرة ، مثل PVS-Studio. إذا كنت أحد أبناء الأرض - مثل ذلك ، أقترح عليك التنزيل وتجربته بنفسك .

أتمنى أن تكون مهتمًا بقراءة مقالتي ، وأنك تعلمت شيئًا جديدًا ومفيدًا لك. وأتمنى للمطورين تصحيح مبكر للأخطاء الموجودة.

اشترك في قنواتنا واستمر في الاطلاع على الأخبار من عالم البرمجة!



إذا كنت ترغب في مشاركة هذا المقال مع جمهور ناطق باللغة الإنجليزية ، فيرجى استخدام الرابط الخاص بالترجمة: George Gribkov. في الفضاء مرة أخرى: كيف زار يونيكورن Stellarium

Source: https://habr.com/ru/post/ar432954/


All Articles