פתרון השחתת זיכרון וחקירת תקיעות¶
פתרון התרגיל הראשון¶
הפונקציה הבאה מייצגת את החוזה המתוקן. היא מקבלת source וdestination עם גדלים, דוחה קלט גדול לפני שינוי היעד ומעתיקה בדיוק את מספר הבתים שנמסר. זהו קובץ בדיקה מלא ללא חריגת זיכרון מכוונת.
#include <array>
#include <algorithm>
#include <span>
#include <stdexcept>
bool copy_checked(std::span<unsigned char> target,
std::span<const unsigned char> source) {
if (source.size() > target.size()) return false;
std::copy(source.begin(), source.end(), target.begin());
return true;
}
int main() {
std::array<unsigned char, 33> source{};
source.fill(7);
for (std::size_t size : {0u, 31u, 32u, 33u}) {
std::array<unsigned char, 32> target{};
const bool copied = copy_checked(target, std::span(source).first(size));
if (copied != (size <= target.size())) throw std::runtime_error("wrong status");
for (std::size_t i = 0; i < target.size(); ++i) {
const unsigned char expected = copied && i < size ? 7 : 0;
if (target[i] != expected) throw std::runtime_error("wrong bytes");
}
}
}
החוזה כאן מניח שהטווחים אינם חופפים באופן שאינו מותר לstd::copy. אם המוצר צריך העתקה עם overlap, נבחר פעולה שמתאימה לכך ונוסיף בדיקה נפרדת. גם span יכול להיווצר בטעות מכתובת לא תקפה; הוא מבטא גודל ואינו מוכיח בעלות או חיי זיכרון.
פתרון התרגיל השני¶
הקובץ הבא שומר התחלה מתואמת אבל מסיר את החזקת lock בודד בזמן barrier. שני הסדרים ברשימת הפרמטרים אינם יוצרים deadlock, משום שscoped_lock משתמש באלגוריתם רכישה משותף מתאים.
#include <barrier>
#include <mutex>
#include <thread>
#include <stdexcept>
int main() {
std::mutex first, second;
std::barrier ready(2);
int completed = 0;
std::thread a([&] {
ready.arrive_and_wait();
std::scoped_lock hold(first, second);
++completed;
});
std::thread b([&] {
ready.arrive_and_wait();
std::scoped_lock hold(second, first);
++completed;
});
a.join();
b.join();
if (completed != 2) throw std::runtime_error("work missing");
}
במוצר נצטרך לטפל גם בכשל יצירת thread לפני שהמשתתפים הגיעו לbarrier. הדוגמה ממקדת את בדיקת סדר הנעילות בהנחה ששני הthreadים נוצרו; מבנה עם barrier שסופר משתתף שלא נוצר יכול להיתקע מסיבה חדשה. במימוש עמיד ניצור את המשאבים הדרושים לפני שחרור המשתתפים לעבודה, או נעדכן את ההשתתפות במסלול כשל.
פתרון התרגיל השלישי¶
הworker יכול להמתין לI/O שטרם הסתיים, לlock שמוחזק על ידי thread אחר, או לcallback שצריך לרוץ על הthread הראשי. נבקש מידע על פעולת הI/O והזמן שלה, בעלות על אובייקט ההמתנה, וstacks של כל המשתתפים. אם הUI הוא היעד של callback, גם לולאת ההודעות והapartment הם חלק מהחקירה.
פקיעת timeout יכולה להחזיר שליטה לcaller בזמן שהפעולה המקורית ממשיכה. אם בתגובה נשחרר context, ייתכן שהחלפנו תקיעה בuse-after-free. התיקון צריך להגדיר גם cancellation, completion ושחרור, ולא רק להפסיק להמתין.