Sementara Stockholm mengadakan Pekan Nobel ke-118, saya duduk di kantor kami, tempat kami mengembangkan penganalisa statis PVS-Studio, mengerjakan kajian analisis proyek ROOT, kerangka kerja pemrosesan data besar yang digunakan dalam penelitian ilmiah. Kode ini tidak akan memenangkan hadiah, tentu saja, tetapi penulis pasti dapat mengandalkan ulasan terperinci dari cacat yang paling menarik plus lisensi gratis untuk memeriksa sendiri proyek tersebut secara menyeluruh.
Pendahuluan
ROOT adalah perangkat lunak perangkat lunak ilmiah modular. Ini menyediakan semua fungsi yang diperlukan untuk menangani pemrosesan data besar, analisis statistik, visualisasi, dan penyimpanan. Ini terutama ditulis dalam C ++. ROOT lahir di
CERN , di jantung penelitian tentang fisika energi tinggi. Setiap hari, ribuan fisikawan menggunakan aplikasi ROOT untuk menganalisis data mereka atau melakukan simulasi.
PVS-Studio adalah alat untuk mendeteksi bug perangkat lunak dan kerentanan potensial dalam kode sumber program yang ditulis dalam C, C ++, C #, dan Java. Ini berjalan pada Windows 64-bit, Linux, dan macOS dan dapat menganalisis kode sumber yang ditulis untuk platform ARM 32-bit, 64-bit, dan tertanam.
Debut diagnostik baru
V1046 Penggunaan bool 'dan' int 'yang tidak aman secara bersamaan dalam operasi' & = '. GSLMultiRootFinder.h 175
int AddFunction(const ROOT::Math::IMultiGenFunction & func) { ROOT::Math::IMultiGenFunction * f = func.Clone(); if (!f) return 0; fFunctions.push_back(f); return fFunctions.size(); } template<class FuncIterator> bool SetFunctionList( FuncIterator begin, FuncIterator end) { bool ret = true; for (FuncIterator itr = begin; itr != end; ++itr) { const ROOT::Math::IMultiGenFunction * f = *itr; ret &= AddFunction(*f); } return ret; }
Pertama, ini adalah bug luar biasa yang ditemukan oleh versi beta PVS-Studio, yang saya gunakan untuk ulasan ini.
Harapan Fungsi
SetFunctionList melintasi daftar iterator. Jika setidaknya satu iterator tidak valid, fungsi mengembalikan
false , atau
true sebaliknya.
Realita Fungsi
SetFunctionList dapat mengembalikan
false bahkan untuk iterator yang valid. Mari kita cari tahu alasannya. Fungsi
AddFunction mengembalikan jumlah iterator yang valid pada daftar
fFunctions . Artinya, menambahkan iterator non-nol akan menyebabkan daftar semakin bertambah ukurannya: 1, 2, 3, 4, dan seterusnya. Di sinilah bug berperan:
ret &= AddFunction(*f);
Karena fungsi mengembalikan nilai tipe
int daripada
bool , operasi '& =' akan mengembalikan
false untuk nilai genap karena bit paling tidak signifikan dari angka genap selalu disetel ke nol. Ini adalah bagaimana salah satu bug halus dapat mematahkan nilai kembali dari SetFunctionsList bahkan ketika argumen yang valid.
Kesalahan dalam ekspresi bersyarat
V501 Ada sub-ekspresi identik ke kiri dan ke kanan operator '&&': module && module rootcling_impl.cxx 3650
virtual void HandleDiagnostic(....) override { .... bool isROOTSystemModuleDiag = module && ....; bool isSystemModuleDiag = module && module && module->IsSystem; if (!isROOTSystemModuleDiag && !isSystemModuleDiag) fChild->HandleDiagnostic(DiagLevel, Info); .... }
Mari kita mulai dengan bug yang paling tidak berbahaya. Penunjuk
modul diperiksa dua kali. Salah satu pemeriksaan mungkin berlebihan, namun masih akan bijaksana untuk memperbaikinya untuk menghindari kebingungan di masa depan.
V501 Ada sub-ekspresi identik 'strchr (fHostAuth-> GetHost (),' * ')' di sebelah kiri dan di sebelah kanan '||' operator. TAuthenticate.cxx 300
TAuthenticate::TAuthenticate(TSocket *sock, const char *remote, const char *proto, const char *user) { ....
String fHostAuth-> GetHost () dipindai untuk karakter '*' dua kali. Salah satu cek ini mungkin dimaksudkan untuk mencari '?' karakter seperti dua karakter ini biasanya yang digunakan untuk menentukan berbagai topeng karakter.
V517 Penggunaan
pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 163, 165. TProofMonSenderML.cxx 163
Int_t TProofMonSenderML::SendSummary(TList *recs, const char *id) { .... if (fSummaryVrs == 0) { if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn); } else if (fSummaryVrs == 0) {
Variabel
fSummaryVrs dibandingkan dengan nol dua kali, jadi eksekusi tidak pernah mencapai kode di cabang
else-if . Dan ada sedikit kode di sana ...
V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. TKDTree.cxx 805
template <typename Index, typename Value> void TKDTree<Index, Value>::UpdateRange(....) { .... if (point[fAxis[inode]]<=fValue[inode]){
Blok kode yang sama, yang merupakan tiruan salin-tempel, dijalankan apa pun kondisinya. Saya kira ada kebingungan antara kata-kata
kiri dan
kanan .
Proyek ini penuh dengan bintik-bintik yang mencurigakan seperti itu:
- V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. TContainerConverters.cxx 51
- V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. TWebFile.cxx 1310
- V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. MethodMLP.cxx 423
- V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. RooAbsCategory.cxx 394
Ekspresi
V547 '! File_name_value.empty ()' selalu salah. SelectionRules.cxx 1423
bool SelectionRules::AreAllSelectionRulesUsed() const { for(auto&& rule : fClassSelectionRules){ .... std::string file_name_value; if (!rule.GetAttributeValue("file_name", file_name_value)) file_name_value.clear(); if (!file_name_value.empty()) {
Ini mungkin bukan bug; penganalisa baru saja menemukan beberapa kode yang dapat disederhanakan. Karena nilai pengembalian
file_name_value.empty () sudah diperiksa pada awal loop, pemeriksaan duplikat kedua dapat dihapus, sehingga membuang sejumlah besar kode yang tidak perlu.
V590 Pertimbangkan untuk memeriksa '! File1 || c <= 0 || c == '*' || c! = '(' 'ekspresi. Ekspresi ini berlebihan atau mengandung salah cetak. TTabCom.cxx 840
TString TTabCom::DetermineClass(const char varName[]) { .... c = file1.get(); if (!file1 || c <= 0 || c == '*' || c != '(') { Error("TTabCom::DetermineClass", "variable \"%s\" not defined?", varName); goto cleanup; } .... }
Inilah bagian masalah dari ekspresi kondisional yang dilaporkan oleh penganalisa:
if (.... || c == '*' || c != '(') { .... }
Pemeriksaan untuk karakter tanda bintang tidak akan mempengaruhi hasil kondisi. Bagian ini akan selalu berlaku untuk karakter apa pun selain '('. Anda dapat dengan mudah memeriksanya sendiri dengan menggambar tabel kebenaran.
Dua peringatan lagi tentang kondisi dengan logika aneh:
- V590 Pertimbangkan untuk memeriksa ungkapan ini. Ekspresi berlebihan atau mengandung kesalahan cetak. TFile.cxx 3963
- V590 Pertimbangkan untuk memeriksa ungkapan ini. Ekspresi berlebihan atau mengandung kesalahan cetak. TStreamerInfoActions.cxx 3084
V593 Pertimbangkan untuk meninjau ekspresi dari jenis 'A = B <C'. Ekspresi dihitung sebagai berikut: 'A = (B <C)'. TProofServ.cxx 1903
Int_t TProofServ::HandleSocketInput(TMessage *mess, Bool_t all) { .... if (Int_t ret = fProof->AddWorkers(workerList) < 0) { Error("HandleSocketInput:kPROOF_GETSLAVEINFO", "adding a list of worker nodes returned: %d", ret); } .... }
Bug ini mengungkapkan dirinya hanya dalam kasus perilaku program yang salah. Variabel
ret seharusnya menyimpan kode
pengembalian fungsi
AddWorkers dan menulis nilai itu ke log jika ada kondisi kesalahan. Tetapi itu tidak berfungsi sebagaimana dimaksud. Kondisi ini tidak memiliki tanda kurung tambahan yang memaksa urutan evaluasi yang diinginkan. Apa yang sebenarnya disimpan oleh variabel
ret bukanlah kode pengembalian tetapi hasil dari perbandingan logis, yaitu 0 atau 1.
Masalah serupa lainnya:
- V593 Pertimbangkan untuk meninjau ekspresi dari jenis 'A = B <C'. Ekspresi dihitung sebagai berikut: 'A = (B <C)'. TProofServ.cxx 3897
V768 Konstanta enumerasi 'kCostComplexityPruning' digunakan sebagai variabel tipe-Boolean. MethodDT.cxx 283
enum EPruneMethod {kExpectedErrorPruning=0, kCostComplexityPruning, kNoPruning}; void TMVA::MethodDT::ProcessOptions() { .... if (fPruneStrength < 0) fAutomatic = kTRUE; else fAutomatic = kFALSE; if (fAutomatic && fPruneMethod==!DecisionTree::kCostComplexityPruning){ Log() << kFATAL << "Sorry automatic pruning strength determination is ...." << Endl; } .... }
Hm ... Kenapa meniadakan nilai konstan kCostComplexityPruning ? Saya menduga karakter negasi adalah kesalahan ketik, yang sekarang mendistorsi logika eksekusi.
Pointer menangani kesalahan
V522 Dereferencing dari pointer nol 'pre' mungkin terjadi. TSynapse.cxx 61
void TSynapse::SetPre(TNeuron * pre) { if (pre) { Error("SetPre","this synapse is already assigned to a pre-neuron."); return; } fpre = pre; pre->AddPost(this); }
Saya melakukan yang terbaik untuk mencoba memahami kode aneh ini, dan sepertinya idenya adalah untuk menghindari pemberian nilai baru ke bidang
fpre . Jika demikian, programmer secara tidak sengaja memeriksa pointer yang salah. Implementasi saat ini mengarah ke mendereferensi pointer nol jika Anda melewatkan nilai
nullptr ke fungsi
SetPre .
Saya pikir cuplikan ini harus diperbaiki sebagai berikut:
void TSynapse::SetPre(TNeuron * pre) { if (fpre) { Error("SetPre","this synapse is already assigned to a pre-neuron."); return; } fpre = pre; pre->AddPost(this); }
Ini, bagaimanapun, tidak akan mencegah lewat dari null pointer ke fungsi, tapi setidaknya versi ini lebih logis konsisten daripada yang asli.
Klon yang sedikit dimodifikasi dari kode ini dapat ditemukan di tempat lain:
- V522 Dereferencing dari 'pointer' pointer nol mungkin terjadi. TSynapse.cxx 74
V595 Pointer 'N' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 484, 488. Scanner.cxx 484
bool RScanner::shouldVisitDecl(clang::NamedDecl *D) { if (auto M = D->getOwningModule()) {
Ini adalah kode yang sangat berbahaya! Pointer
N tidak dicentang untuk null sebelum direferensikan pertama kali. Terlebih lagi, Anda tidak dapat melihat itu terjadi di sini karena dereference terjadi di dalam fungsi
shouldVisitDecl .
Diagnosis ini secara tradisional menghasilkan banyak peringatan yang relevan. Berikut ini beberapa contohnya:
- V595 Penunjuk 'file' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 141, 153. TFileCacheRead.cxx 141
- V595 Pointer 'fFree' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 2029, 2038. TFile.cxx 2029
- V595 Pointer 'tbuf' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 586, 591. TGText.cxx 586
- V595 Pointer 'fPlayer' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 3425, 3430. TProof.cxx 3425
- V595 Pointer 'gProofServ' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 1192, 1194. TProofPlayer.cxx 1192
- V595 Pointer 'projDataTmp' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 791, 804. RooSimultaneous.cxx 791
Yang berikutnya bukan bug, tapi itu contoh lain bagaimana makro
mendorong penulisan kode yang salah atau berlebihan.
V571 Cek berulang. Kondisi 'jika (fCanvasImp)' sudah diverifikasi di baris 799. TCanvas.cxx 800
#define SafeDelete(p) { if (p) { delete p; p = 0; } } void TCanvas::Close(Option_t *option) { .... if (fCanvasImp) SafeDelete(fCanvasImp); .... }
Pointer
fCanvasImp diperiksa dua kali, dengan salah satu pemeriksaan sudah diterapkan di makro
SafeDelete . Salah satu masalah dengan makro adalah bahwa mereka sulit dinavigasi dari dalam kode, yang merupakan alasan mengapa banyak programmer tidak memeriksa isinya sebelum digunakan.
Array menangani kesalahan
V519 Variabel 'Baris [Kursor]' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 352, 353. Editor.cpp 353
size_t find_last_non_alnum(const std::string &str, std::string::size_type index = std::string::npos) { .... char tmp = Line.GetText()[Cursor]; Line[Cursor] = Line[Cursor - 1]; Line[Cursor] = tmp; .... }
]; size_t find_last_non_alnum(const std::string &str, std::string::size_type index = std::string::npos) { .... char tmp = Line.GetText()[Cursor]; Line[Cursor] = Line[Cursor - 1]; Line[Cursor] = tmp; .... }
Baris Elemen
[Kursor] diberi nilai baru, yang kemudian segera ditimpa. Itu tidak terlihat benar ...
V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 130
bool BasicMinimizer::SetVariableValue(unsigned int ivar, double val) { if (ivar > fValues.size() ) return false; fValues[ivar] = val; return true; }
Membuat kesalahan ini saat memeriksa indeks array adalah tren terbaru; kami melihatnya di hampir setiap proyek ketiga. Meskipun pengindeksan ke dalam array di dalam loop mudah - Anda biasanya menggunakan operator '<' untuk membandingkan indeks dengan ukuran array - memeriksa seperti yang ditunjukkan di atas memerlukan operator '> =', bukan '>'. Kalau tidak, Anda berisiko mengindeks satu elemen di luar batas array.
Bug ini telah dikloning di seluruh kode beberapa kali:
- V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 186
- V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 194
- V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 209
- V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 215
- V557 Array overrun dimungkinkan. Indeks 'ivar' menunjuk di luar batas array. BasicMinimizer.cxx 230
V621 Pertimbangkan untuk memeriksa operator 'untuk'. Ada kemungkinan bahwa loop akan dieksekusi secara tidak benar atau tidak akan dieksekusi sama sekali. TDataMember.cxx 554
Int_t TDataMember::GetArrayDim() const { if (fArrayDim<0 && fInfo) { R__LOCKGUARD(gInterpreterMutex); TDataMember *dm = const_cast<TDataMember*>(this); dm->fArrayDim = gCling->DataMemberInfo_ArrayDim(fInfo);
Dalam
for loop, para pengembang tampaknya bermaksud untuk membandingkan variabel
redup dengan
dm-> fArrayDim daripada
fArrayDim . Nilai
fArrayDim negatif, yang dijamin oleh kondisi di awal fungsi. Akibatnya, loop ini tidak akan pernah dijalankan.
V767 Akses mencurigakan ke elemen array 'saat ini' dengan indeks konstan di dalam satu loop. TClingUtils.cxx 3082
llvm::StringRef ROOT::TMetaUtils::DataMemberInfo__ValidArrayIndex(....) { .... while (current!=0) {
Kode ini parsing dan memeriksa beberapa string. Jika karakter pertama string
saat ini (yaitu pada indeks 0) telah dikenali sebagai angka, loop akan melintasi semua karakter lainnya untuk memastikan semuanya adalah angka. Yah, setidaknya itulah idenya. Masalahnya adalah, penghitung
i tidak digunakan dalam loop. Kondisi harus ditulis ulang sehingga memeriksa
[i] saat ini daripada
saat ini [0] .
Kebocoran memori
V773 Fungsi itu keluar tanpa melepaskan pointer 'daftar pilihan'. Kebocoran memori dimungkinkan. TDataMember.cxx 355
void TDataMember::Init(bool afterReading) { .... TList *optionlist = new TList();
Pointer
optionList tidak dibebaskan sebelum kembali dari fungsi. Saya tidak tahu apakah pembebasan seperti itu diperlukan dalam kasus khusus ini, tetapi ketika kami melaporkan kesalahan seperti itu, pengembang biasanya memperbaikinya. Itu semua tergantung pada apakah Anda ingin program Anda tetap berjalan jika ada kondisi kesalahan. ROOT memiliki banyak cacat seperti itu, jadi saya akan menyarankan penulis untuk memeriksa kembali proyek itu sendiri.
memset lagi
V597 Kompiler dapat menghapus pemanggilan fungsi 'memset', yang digunakan untuk membersihkan buffer 'x'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. TMD5.cxx 366
void TMD5::Transform(UInt_t buf[4], const UChar_t in[64]) { UInt_t a, b, c, d, x[16]; ....
Banyak yang mengira komentar tidak akan sampai ke file biner setelah kompilasi, dan mereka benar sekali: D. Apa yang mungkin tidak diketahui beberapa orang adalah bahwa kompiler akan menghapus fungsi
memset juga. Dan ini pasti akan terjadi. Jika buffer yang dimaksud tidak lagi digunakan lebih lanjut dalam kode, kompiler akan mengoptimalkan pemanggilan fungsi. Secara teknis, ini adalah keputusan yang masuk akal, tetapi jika buffer menyimpan data pribadi apa pun, data itu akan tetap ada. Ini adalah kelemahan keamanan klasik
CWE-14 .
Lain-lain
Fungsi V591 Non-void harus mengembalikan nilai. LogLikelihoodFCN.h 108
LogLikelihoodFCN & operator = (const LogLikelihoodFCN & rhs) { SetData(rhs.DataPtr() ); SetModelFunction(rhs.ModelFunctionPtr() ); fNEffPoints = rhs.fNEffPoints; fGrad = rhs.fGrad; fIsExtended = rhs.fIsExtended; fWeight = rhs.fWeight; fExecutionPolicy = rhs.fExecutionPolicy; }
Operator kelebihan beban tidak memiliki nilai balik. Ini adalah tren terbaru lainnya.
V596 Objek telah dibuat tetapi tidak sedang digunakan. Kata kunci 'throw' dapat hilang: throw runtime_error (FOO); RTensor.hxx 363
template <typename Value_t, typename Container_t> inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Transpose() { if (fLayout == MemoryLayout::RowMajor) { fLayout = MemoryLayout::ColumnMajor; } else if (fLayout == MemoryLayout::ColumnMajor) { fLayout = MemoryLayout::RowMajor; } else { std::runtime_error("Memory layout is not known."); } .... }
Masalahnya adalah bahwa programmer tidak sengaja meninggalkan kata kunci
throw , sehingga mencegah pelemparan pengecualian jika terjadi kesalahan.
Hanya ada dua peringatan dari jenis ini. Inilah yang kedua:
- V596 Objek telah dibuat tetapi tidak sedang digunakan. Kata kunci 'throw' dapat hilang: throw runtime_error (FOO); Forest.hxx 137
V609 Bagilah dengan nol. Kisaran penyebut [0..100]. TGHtmlImage.cxx 340
const char *TGHtml::GetPctWidth(TGHtmlElement *p, char *opt, char *ret) { int n, m, val; .... if (n < 0 || n > 100) return z; if (opt[0] == 'h') { val = fCanvas->GetHeight() * 100; } else { val = fCanvas->GetWidth() * 100; } if (!fInTd) { snprintf(ret, 15, "%d", val / n);
Yang ini mirip dengan contoh penanganan array yang dibahas sebelumnya. Variabel
n terbatas pada rentang dari 0 hingga 100. Tapi kemudian ada cabang yang melakukan pembagian oleh variabel
n yang mungkin memiliki nilai 0. Saya pikir batas kisaran
n harus diperbaiki sebagai berikut:
if (n <= 0 || n > 100) return z;
V646 Pertimbangkan Memeriksa logika aplikasi. Mungkin kata kunci 'lain' tidak ada. TProofServ.cxx 729
TProofServ::TProofServ(Int_t *argc, char **argv, FILE *flog) : TApplication("proofserv", argc, argv, 0, -1) { .... if (!logmx.IsDigit()) { if (logmx.EndsWith("K")) { xf = 1024; logmx.Remove(TString::kTrailing, 'K'); } else if (logmx.EndsWith("M")) { xf = 1024*1024; logmx.Remove(TString::kTrailing, 'M'); } if (logmx.EndsWith("G")) { xf = 1024*1024*1024; logmx.Remove(TString::kTrailing, 'G'); } } .... }
, argv, TProofServ::TProofServ(Int_t *argc, char **argv, FILE *flog) : TApplication("proofserv", argc, argv, 0, -1) { .... if (!logmx.IsDigit()) { if (logmx.EndsWith("K")) { xf = 1024; logmx.Remove(TString::kTrailing, 'K'); } else if (logmx.EndsWith("M")) { xf = 1024*1024; logmx.Remove(TString::kTrailing, 'M'); } if (logmx.EndsWith("G")) { xf = 1024*1024*1024; logmx.Remove(TString::kTrailing, 'G'); } } .... }
Penganalisa melaporkan pernyataan
if yang diformat aneh dengan kata kunci
lain yang hilang. Tampilan kode ini menunjukkan bahwa kode itu perlu diperbaiki.
Beberapa lagi peringatan dari jenis ini:
- V646 Pertimbangkan untuk memeriksa logika aplikasi. Mungkin kata kunci 'lain' tidak ada. TFormula_v5.cxx 3702
- V646 Pertimbangkan untuk memeriksa logika aplikasi. Mungkin kata kunci 'lain' tidak ada. RooAbsCategory.cxx 604
V663 Infinite loop dimungkinkan. Kondisi 'cin.eof ()' tidak cukup untuk memutuskan dari loop. Pertimbangkan untuk menambahkan pemanggilan fungsi 'cin.fail ()' ke ekspresi kondisional. MethodKNN.cxx 602
void TMVA::MethodKNN::ReadWeightsFromStream(std::istream& is) { .... while (!is.eof()) { std::string line; std::getline(is, line); if (line.empty() || line.find("#") != std::string::npos) { continue; } .... } .... }
Ketika bekerja dengan kelas
std :: istream , memanggil fungsi
eof () tidak cukup untuk mengakhiri loop.
The eof () fungsi akan selalu kembali false jika data tidak dapat dibaca, dan tidak ada titik terminasi lain dalam kode ini. Untuk menjamin berakhirnya loop, diperlukan pemeriksaan tambahan untuk nilai yang dikembalikan oleh fungsi
gagal () :
while (!is.eof() && !is.fail()) { .... }
Sebagai alternatif, dapat ditulis ulang sebagai berikut:
while (is) { .... }
V678 Objek digunakan sebagai argumen untuk metode sendiri. Pertimbangkan untuk memeriksa argumen aktual pertama dari fungsi 'Salin'. TFormLeafInfo.cxx 2414
TFormLeafInfoMultiVarDim::TFormLeafInfoMultiVarDim( const TFormLeafInfoMultiVarDim& orig) : TFormLeafInfo(orig) { fNsize = orig.fNsize; fSizes.Copy(fSizes);
orig) TFormLeafInfoMultiVarDim::TFormLeafInfoMultiVarDim( const TFormLeafInfoMultiVarDim& orig) : TFormLeafInfo(orig) { fNsize = orig.fNsize; fSizes.Copy(fSizes);
Mari kita selesaikan artikel dengan kesalahan ketik kecil yang menyenangkan ini. Fungsi
Salin harus dipanggil dengan
orig.fSizes , bukan
fSizes .
Kesimpulan
Satu tahun yang lalu Tentang, Situs kami memeriksa Hanya
NCBI Workbench Genome proyek, yang merupakan program lain Digunakan dalam penelitian ilmiah yang berkaitan dengan analisis genom. Saya menyebutkan ini karena kualitas perangkat lunak ilmiah sangat penting, namun pengembang cenderung meremehkannya.
Omong-omong, macOS 10.15 Catalina dirilis kemarin, di mana mereka berhenti mendukung aplikasi 32-bit. Untungnya, PVS-Studio menawarkan serangkaian besar diagnostik yang dirancang khusus untuk mendeteksi bug yang menyertai porting program ke sistem 64-bit. Pelajari lebih lanjut di
pos ini oleh tim PVS-Studio.