\documentclass[10pt]{article} \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage[polish]{babel} \usepackage[a4paper,margin=1.55cm]{geometry} \usepackage{array,tabularx,booktabs} \usepackage{amsmath,amssymb} \usepackage{xcolor,listings} \usepackage{hyperref,fancyhdr,lastpage,enumitem} \definecolor{accent}{HTML}{16324A} \definecolor{accentlight}{HTML}{EEF3F7} \definecolor{rulegray}{HTML}{D7DEE5} \hypersetup{colorlinks=true,linkcolor=accent,urlcolor=blue} \IfFileExists{build-meta.tex}{\input{build-meta.tex}}{\newcommand{\BuildCommit}{local}} \newcommand{\PublisherDomain}{mpabi} \newcommand{\CardArea}{inf} \newcommand{\CardSeries}{freertos-cpp} \newcommand{\CardNumber}{03} \newcommand{\CardCount}{16} \newcommand{\CardSlug}{vector-raii} \newcommand{\CardVersion}{v00.01} \newcommand{\DocumentUUID}{de1a2818-4c30-5760-8b31-255114f82e90} \newcommand{\blank}[1]{\rule{#1}{.2pt}} \lstset{ language=C++,basicstyle=\ttfamily\scriptsize,columns=fullflexible, keepspaces=true,frame=single,breaklines=true,showstringspaces=false, numbers=none,backgroundcolor=\color{accentlight},rulecolor=\color{rulegray} } \pagestyle{fancy} \fancyhf{} \lhead{\textbf{K03 · FreeRTOS C++ · \texttt{VectorV1}}} \rhead{\small L02 · ownership i RAII} \lfoot{\scriptsize commit \BuildCommit} \cfoot{\scriptsize \thepage/\pageref{LastPage}} \rfoot{\scriptsize V11.3.0 / \CardVersion} \setlength{\headheight}{14pt} \setlength{\footskip}{19pt} \setlist[itemize]{nosep,leftmargin=1.45em} \setlist[enumerate]{nosep,leftmargin=1.65em} \begin{document} \sloppy \begin{center} {\LARGE\bfseries \texttt{VectorV1}: destruktor, move i jeden właściciel}\par \vspace{.25em} {\large od wycieku i płytkiej kopii do obserwowalnego RAII}\par \end{center} \noindent\begin{tabularx}{\textwidth}{@{}p{1.65cm}Xp{1.55cm}X@{}} \toprule Karta & K03 / \CardCount & Czas & 45 minut \\ Platforma & Hazard3 / RV32I & Język & freestanding C++17 \\ Allocator & FreeRTOS \texttt{heap\_4} & Kernel & V11.3.0, bez zmian \\ Wersja & \CardVersion & UUID karty & \texttt{de1a2818-...} \\ \bottomrule \end{tabularx} \section*{Punkt startowy: celowo niebezpieczny Vector V0} \begin{lstlisting} class VectorV0 { public: explicit VectorV0(size_t n) : elements_{n ? new double[n] : nullptr}, size_{n} {} private: double *elements_; size_t size_; }; // brak destruktora; kopiowanie byloby plytkie \end{lstlisting} \noindent\fcolorbox{accent}{accentlight}{% \begin{minipage}{.94\textwidth} \textbf{Inwariant K03.} Każdy żywy bufor ma dokładnie jednego właściciela. Obiekt po przeniesieniu jest pusty: \texttt{data()==nullptr} i \texttt{size()==0}. \end{minipage}} \section*{Najpierw diagnoza} \begin{tabularx}{\textwidth}{@{}p{4.2cm}X@{}} \toprule Operacja na V0 & Co może pójść źle? \\ \midrule wyjście z zakresu & \blank{9cm} \\ domyślna kopia wskaźnika & \blank{9cm} \\ dodanie destruktora bez zakazu kopii & \blank{9cm} \\ przypisanie do obiektu, który już ma bufor & \blank{9cm} \\ \bottomrule \end{tabularx} \section*{Plan lekcji} \begin{tabularx}{\textwidth}{@{}p{1.5cm}p{3.0cm}X@{}} \toprule Czas & Tryb & Dowód \\ \midrule 0--5 & V0 & wyciek, płytka kopia, brak reguły właściciela \\ 5--11 & destruktor & \texttt{delete[]} prowadzi do \texttt{vPortFree()} \\ 11--17 & copy / move & copy usunięte; źródło po move jest puste \\ 17--25 & move assignment & \texttt{release -> take -> clear source} \\ 25--38 & Hazard3/GDB & ten sam adres A, zwolnienie B, heap wraca do baseline \\ 38--45 & ćwiczenie i wyjście & kolejność operacji oraz granica RAII \\ \bottomrule \end{tabularx} \newpage \section{Co naprawdę posiada obiekt?} \begin{center} \texttt{VectorV1 object: [ elements\_=A | size\_=4 ]} \qquad$\longrightarrow$\qquad \texttt{heap\_4: [ A: 4 * double ]} \end{center} Obiekt ma dwa pola i może leżeć na stosie. Dane są osobnym blokiem w \texttt{ucHeap}. Destruktor zwalnia \emph{bufor}, nie pamięć samego obiektu. \subsection*{Pięć operacji właściciela} \begin{tabularx}{\textwidth}{@{}p{3.3cm}p{2.7cm}X@{}} \toprule Operacja & K03 & Powód \\ \midrule destruktor & implementowany & zwalnia jedyny posiadany bufor \\ copy constructor & \texttt{= delete} & płytka kopia stworzyłaby dwóch właścicieli \\ copy assignment & \texttt{= delete} & ten sam problem oraz możliwa utrata starego celu \\ move constructor & \texttt{noexcept} & przejmuje adres, czyści źródło \\ move assignment & \texttt{noexcept} & najpierw zwalnia stary bufor celu \\ \bottomrule \end{tabularx} \subsection*{Destruktor i stan pusty} \begin{lstlisting} ~VectorV1() noexcept { release(); } void release() noexcept { delete[] elements_; // delete[] nullptr jest bezpieczne elements_ = nullptr; size_ = 0; } \end{lstlisting} \subsection*{Move constructor: nowy obiekt nie ma starego zasobu} \begin{lstlisting} VectorV1(VectorV1&& other) noexcept : elements_{other.elements_}, size_{other.size_} { other.elements_ = nullptr; other.size_ = 0; } \end{lstlisting} \textbf{Predykcja.} Przed move: \texttt{source.elements\_=A}. Po move: \begin{center} \texttt{new.elements\_=\blank{1.5cm}}\qquad \texttt{source.elements\_=\blank{1.5cm}}\qquad liczba \texttt{delete[]} podczas samego move: \blank{1cm} \end{center} \vfill \noindent\textbf{Wniosek:} move przenosi prawo do późniejszego zwolnienia. Nie kopiuje bufora i nie alokuje drugiego. \newpage \section{Move assignment do zajętego celu} \begin{center} \texttt{source -> A}\qquad\texttt{destination -> B}\qquad $A\neq B$ \end{center} Cel już posiada B. Wpisz numery 1--5 w jedynej bezpiecznej kolejności: \noindent\begin{tabularx}{\textwidth}{@{}p{1.5cm}X@{}} \toprule Kolejność & Operacja \\ \midrule \blank{1cm} & \texttt{other.elements\_ = nullptr; other.size\_ = 0;} \\ \blank{1cm} & \texttt{elements\_ = other.elements\_;} \\ \blank{1cm} & sprawdź \texttt{this != \&other} \\ \blank{1cm} & \texttt{release();} \\ \blank{1cm} & \texttt{size\_ = other.size\_;} \\ \bottomrule \end{tabularx} \subsection*{Sprawdź cztery błędne warianty} \begin{enumerate} \item Najpierw nadpisz \texttt{elements\_} adresem A. Co stało się z B? \item Po przejęciu A wykonaj \texttt{release()}. Który bufor zwolnisz? \item Nie wyzeruj źródła. Ile obiektów uważa, że posiada A? \item Pomiń kontrolę self-move. Co zrobi \texttt{x = move(x)}? \end{enumerate} \subsection*{Odsłonięcie po predykcji} \begin{lstlisting} if (this != &other) { release(); // free B elements_ = other.elements_; // take A size_ = other.size_; other.elements_ = nullptr; // source no longer owns A other.size_ = 0; } \end{lstlisting} \section*{Most C++ $\rightarrow$ FreeRTOS C} \begin{center} \texttt{\~VectorV1 -> delete[] -> operator delete[] -> vPortFree} \end{center} Most alokacji definiuje komplet: \texttt{new}, \texttt{new[]}, zwykłe i tablicowe \texttt{delete}, każde w wariancie sized oraz unsized. Zwykłe \texttt{new} w tym profilu nie może zwrócić \texttt{nullptr}; błąd alokacji kończy się kontrolowanym fail-fast. \newpage \section{Hazard3/GDB: śledź A i B, nie nazwy zmiennych} \begin{lstlisting}[language=bash] make check riscv64-unknown-elf-gdb build/task01_vector_raii/prog.elf b vector_raii_debug_checkpoint \end{lstlisting} \noindent\begin{tabularx}{\textwidth}{@{}p{1cm}p{4.2cm}X@{}} \toprule STOP & Stan & Obowiązkowa obserwacja \\ \midrule 1 & baseline & \texttt{g\_initial\_free} zapisane \\ 2 & source ma A & allocation count = 1; A leży w \texttt{ucHeap} \\ 3 & source ma A, destination ma B & dwa różne adresy; free spadło \\ 4 & assignment wykonany & pierwszy free to B; destination ma A; source puste \\ 5 & move construction & final owner ma nadal A; destination puste \\ 6 & final owner poza zakresem & drugi free to A; free wróciło do baseline \\ 7 & wynik & allocations=2, frees=2, live=0, pass=1 \\ \bottomrule \end{tabularx} \subsection*{Minimalny zestaw poleceń} \begin{lstlisting} p g_last_checkpoint p/x g_source_buffer_before_move p/x g_destination_old_buffer p/x g_destination_buffer_after_assignment p/x g_final_buffer_after_construction p g_cpp_allocation_count p g_cpp_deallocation_count p g_cpp_live_allocations p g_initial_free p g_after_two_owners_free p g_after_scope_free p g_minimum_ever_free \end{lstlisting} \section*{Relacje do wpisania} \begin{tabularx}{\textwidth}{@{}X p{4.2cm}@{}} \toprule Warunek & Odczyt / dowód \\ \midrule \texttt{after\_two\_owners < initial} & \blank{4cm} \\ \texttt{minimum\_ever <= after\_two\_owners} & \blank{4cm} \\ \texttt{after\_scope == initial} & \blank{4cm} \\ adres po assignment = A & \blank{4cm} \\ adres po move construction = A & \blank{4cm} \\ kolejność freed addresses = B, A & \blank{4cm} \\ \bottomrule \end{tabularx} \newpage \section*{Wyjście — odpowiedz bez uruchamiania programu} \begin{enumerate} \item Dlaczego samo dodanie destruktora do płytko kopiowalnego V0 pogarsza błąd z wycieku do możliwego double free?\\[1.5em] \item Dlaczego move assignment musi zwolnić B przed przejęciem A?\\[1.5em] \item Dlaczego \texttt{other=nullptr} jest zmianą własności, a nie tylko kosmetycznym „czyszczeniem”?\\[1.5em] \item Kiedy destruktor C++ nie pomoże: normalny koniec zakresu czy wymuszone usunięcie taska bez unwindingu?\\[1.5em] \end{enumerate} \section*{Zaliczenie} \begin{itemize} \item $\square$ formułuję inwariant „jeden bufor --- jeden właściciel”; \item $\square$ uzasadniam \texttt{copy = delete} i \texttt{move noexcept}; \item $\square$ układam \texttt{release -> take -> clear source}; \item $\square$ wskazuję A po obu move oraz B jako pierwszy zwolniony adres; \item $\square$ pokazuję \texttt{allocations=2 frees=2 live=0}; \item $\square$ pokazuję \texttt{after\_scope == initial} i \texttt{pass=1}; \item $\square$ łączę \texttt{delete[] -> operator delete[] -> vPortFree()}. \end{itemize} \section*{Granica RAII} RAII działa wtedy, gdy kończy się czas życia obiektu zgodnie z regułami C++. Nie obiecuje automatycznego sprzątania po zaniku zasilania, zatrzymaniu systemu ani arbitralnym \texttt{vTaskDelete()} bez odwinięcia ramek C++. Dlatego późniejszy wrapper taska nie będzie ukrywał wymuszonego usunięcia w destruktorze. \vfill \noindent\textbf{Następna karta K04:} stany schedulera, priorytety, time slicing i typowane ticki. \texttt{VectorV1} pozostaje małym właścicielem pamięci; nie staje się jeszcze kontenerem o zmiennym rozmiarze ani allocator-aware. \end{document}