diff --git a/Makefile b/Makefile index 9c3ef82..f0c6c41 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,11 @@ md-out/%.tex: markdown/%.md single: ${TEX} ${TEXFLAGS} ./${NAME}.tex +optimize: + qpdf --optimize-images output/${NAME}.pdf -- output/${NAME}-optimg.pdf + qpdf --linearize output/${NAME}-optimg.pdf -- output/${NAME}-opt-final.pdf + rm output/${NAME}-optimg.pdf + bib: ${BIB} ${BIBFLAGS} ${NAME} diff --git a/document.yaml b/document.yaml index aa41121..41eca7c 100644 --- a/document.yaml +++ b/document.yaml @@ -22,7 +22,7 @@ page_config: sections: - { path: 'section/introduction.tex', newpg: false } - - { path: 'section/syntax.tex', newpg: false } + - { path: 'section/syntax.tex', newpg: true } - { path: 'section/program-1.tex', newpg: false } - { path: 'section/program-2.tex', newpg: false } - { path: 'section/program-3.tex', newpg: false } diff --git a/main.tex b/main.tex index 1376f12..67bdffa 100644 --- a/main.tex +++ b/main.tex @@ -21,6 +21,7 @@ \input{section/introduction.tex} \input{section/syntax.tex} + \newpage \input{section/program-1.tex} diff --git a/output/main-opt-final.pdf b/output/main-opt-final.pdf new file mode 100644 index 0000000..e698cf9 Binary files /dev/null and b/output/main-opt-final.pdf differ diff --git a/output/main.pdf b/output/main.pdf index d83da00..125612d 100644 Binary files a/output/main.pdf and b/output/main.pdf differ diff --git a/references.bib b/references.bib index 82a2745..5c1597f 100644 --- a/references.bib +++ b/references.bib @@ -27,8 +27,43 @@ @online{gcc_man, title = {Top (Using the GNU Compiler Collection(GCC))}, author = {Contributors of GCC}, + organization = {Inc., Free Software Foundation}, url = {https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/}, year = {2024}, month = {07}, urldate = {2025-04-17} } +@online{cppref_mainfunc, + title = {Main function}, + author = {Cubbi and Newatthis and Ricardolcao and Prota, Gennaro and Fruderica and Pchelper4 and Ybab321 and Neuroevolutus and Mission, Space}, + url = {https://en.cppreference.com/w/c/language/main_function}, + year = {2023}, + month = {07}, + urldate = {2025-04-18} +} +@online{cppref_include, + title = {Source file inclusion}, + author = {P12 and Newatthis and Fruderica and Mission, Space}, + url = {https://en.cppreference.com/w/c/preprocessor/include}, + year = {2023}, + month = {09}, + urldate = {2025-04-18} +} +@online{cppref_stdlibheaders, + title = {C Standard Library headers}, + author = {Bazzy and Cubbi and P12bot and OrangeBuzzard and Newatthis and Yaossg and Fruderica and Mission, Space and D41D8CD98F and YexuanXiao}, + url = {https://en.cppreference.com/w/c/header}, + year = {2025}, + month = {02}, + urldate = {2025-04-18} +} +@booklet{riscv, + title = {RISC-V ASSEMBLY LANGUAGE Programmer Manual Part I}, + author = {SHAKTI Development Team}, + organization = {Shakti@IIT Madras}, + url = {https://shakti.org.in/docs/risc-v-asm-manual.pdf}, + year = {2021}, + month = {01}, + date = {2021-01-14}, + pages = {12-13,33,35-37,61-62,76,80-81} +} diff --git a/section/appendix.tex b/section/appendix.tex index 4d05670..fe2e386 100644 --- a/section/appendix.tex +++ b/section/appendix.tex @@ -4,7 +4,7 @@ \subsection{Deep Dive - 変数の舞台裏} \ref{var_decl_def}節にてプログラムが値の種類を判別するためにコンパイラが型情報に基づいて読み出すべきビット長を実行ファイルに書き込まれると書いたが、実際はどうであろう。 -ここではコンパイラの生成物である実行ファイルをアセンブリに分解して検証していく。なお、この付録を読むのに特別な知識は必要なく、必要に応じて説明する。 +ここではコンパイラの生成物である実行ファイルをアセンブリに分解して検証していく。 まずは検証用のソースコードを用意する。今回は以下の物を用意した: @@ -50,7 +50,6 @@ Linuxでの\texttt{gcc}コンパイラは特に指定されていなければコ \begin{center} \begin{verbatim} - demo: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: @@ -87,7 +86,75 @@ demo: ファイル形式 elf64-x86-64 これはまさに宣言そのもので、\texttt{int}型は32ビットのサイズを持つのでちゃんと当てはまる。 更にこの空いた場所に\texttt{movl}命令を使用して16進数値\texttt{0xff}、10進数の255を移動させている。 これが定義であり、宣言した場所に値を代入するというC言語の\texttt{a = 255;}と一致する。 +\texttt{x86\_64}の様なCISC(複雑命令セットコンピュータ)は宣言と定義を一つの命令で行っている。 \newpage +では、小さく単純な命令セットを持つRISC(縮小命令セットコンピュータ)ではどうだろうか。 +コンパイラをRISC-V 64ビットアーキテクチャ用のツールチェーンに変更して再度検証してみる。 + +まずは、以下のコマンドでコンパイルする: + +\begin{center} + \begin{verbatim} +riscv64-none-elf-gcc-14.2.1 -Wall -nostdlib -nolibc -O0 demo.c -o demo-riscv + \end{verbatim} +\end{center} + +そして以下のコマンドでデコンパイルする: +\begin{center} + \begin{verbatim} +riscv64-none-elf-objdump -d demo-riscv + \end{verbatim} +\end{center} + +デコンパイル結果は以下の通りである: + +\begin{center} + \begin{verbatim} +demo-riscv: ファイル形式 elf64-littleriscv + +セクション .text の逆アセンブル: + +00000000000100e8
: + 100e8: 1101 addi sp,sp,-32 + 100ea: ec06 sd ra,24(sp) + 100ec: e822 sd s0,16(sp) + 100ee: 1000 addi s0,sp,32 + 100f0: 0ff00793 li a5,255 + 100f4: fef42623 sw a5,-20(s0) + 100f8: 0001 nop + 100fa: 60e2 ld ra,24(sp) + 100fc: 6442 ld s0,16(sp) + 100fe: 6105 addi sp,sp,32 + 10100: 8082 ret + \end{verbatim} +\end{center} + +このアセンブリの口語訳は以下となる\cite{riscv}: + +\begin{itemize} + \item \texttt{sp}(スタックポインタ)レジスタに$\textrm{\texttt{sp}レジスタの値}+(-32)$の加算結果を代入する。 + \item \texttt{ra}レジスタから$\textrm{\texttt{sp}レジスタが示めしているアドレス}+24$のアドレスが指している場所に64ビット値をコピーする。 + \item \texttt{s0}レジスタから$\textrm{\texttt{sp}レジスタが示めしているアドレス}+16$のアドレスが指している場所に64ビット値をコピーする。 + \item \texttt{s0}(スタックポインタ)レジスタに$\textrm{\texttt{sp}レジスタの値}+32$の加算結果を代入する。 + \item \texttt{a5}レジスタに10進数値255を代入する。 + \item \texttt{a5}レジスタから$\textrm{\texttt{sp}レジスタが示めしているアドレス}+(-20)$のアドレスが指している場所に32ビット値をコピーする。 + \item なにもしない + \item $\textrm{\texttt{sp}レジスタが示めしているアドレス}+24$のアドレスが指している場所から\texttt{ra}レジスタに64ビット値をロードする。 + \newpage + \item $\textrm{\texttt{sp}レジスタが示めしているアドレス}+16$のアドレスが指している場所から\texttt{s0}レジスタに64ビット値をロードする。 + \item \texttt{sp}(スタックポインタ)レジスタに$\textrm{\texttt{sp}レジスタの値}+32$の加算結果を代入する。 + \item 呼び出し元の関数に制御を戻す。 +\end{itemize} + +やはり、命令の種類が少ないRISCでは命令の数が多くなっている。 +だが最初の4つと最後の4つは関数のスタックフレームに関する命令で無視してよい。 +見るべきものは\texttt{100f0}と\texttt{100f4}である。 +そこでは32ビットの値255を\texttt{a5}レジスタに記憶し、スタックにコピーしている。 +ここでは64ビット長のレジスタに32ビット値を代入することで変数の宣言と定義を同時にしている。 +しかしレジスタは一時的なデータの保持に使用されるので関数の寿命の間、いつでも参照できるようにスタックに移動させているのである。 + +比べてみると、CISCとRISCではやることは同じである:スタックに変数の場所を作り、値を入れる。 + 以上より、\ref{var_decl_def}節で示した値の種類の判別にビット長を実行ファイルに埋め込むということがらが実証された。 diff --git a/section/introduction.tex b/section/introduction.tex index c3523ea..f5cef09 100644 --- a/section/introduction.tex +++ b/section/introduction.tex @@ -8,6 +8,6 @@ \item OS: Arch Linux \item CPU アーキテクチャ: \lstinline[columns=fixed]{x86_64} \item Cコンパイラ: \lstinline[columns=fixed]{gcc (GCC) 14.2.1 20250207} - \item Cコンパイラオプション: \lstinline[columns=fixed]{-Wall <ソースコード名> -o <プログラム名>}\footnote{\texttt{-Wall}はコンパイル時に「凡ミス」や書式に関する全ての警告を表示するフラグである。} + \item Cコンパイラオプション: \lstinline[columns=fixed]{-Wall <ソースコード名> -o <プログラム名>}\footnote{\texttt{-Wall}はコンパイル時に「凡ミス」や書式に関する全ての警告を表示するフラグである。\cite{gcc_man}} \end{itemize} diff --git a/section/program-3.tex b/section/program-3.tex index f065607..8955540 100644 --- a/section/program-3.tex +++ b/section/program-3.tex @@ -2,8 +2,6 @@ 出力に「情報処理」を右に向かって斜めに表示するプログラム -\newpage - \subsection{コードリスティング} \lstinputlisting[language=C, title={演習課題3}]{../programs/prog3.c} diff --git a/section/syntax.tex b/section/syntax.tex index 739511d..8f3ea69 100644 --- a/section/syntax.tex +++ b/section/syntax.tex @@ -1,9 +1,61 @@ \section{今回の構文} +\subsection{\texttt{\#include}マクロ} + +コンパイラはソースコードをコンパイルする前にプリプロセッサを実行し、ファイル内に定義されたマクロを展開する。 +マクロは普通の文と違い、「;」(セミコロン)が文末に無く、マクロ名の先頭に「\#」(ハッシュ記号)が付く。 +\texttt{\#include}はマクロの1つであり、主に関数・変数・定数・型などの宣言が羅列されているヘッダーファイルの内容をマクロが書かれた場所に展開するもので、大抵の場合ソースコードの最上部に書かれている。\cite{cppref_include} + +よく使用されるヘッダーファイルの一部には以下の物がある\cite{cppref_stdlibheaders}: + +\begin{itemize} + \item \texttt{stdlib.h}:標準ライブラリのこと、メモリ管理や頻出アルゴリズムなどに関する関数などが宣言・定義されている + \item \texttt{stdio.h}:標準入出力のこと、ファイル操作などに関する関数が宣言・定義されている + \item \texttt{string.h}:文字列操作によく使用される関数を宣言・定義されている + \item \texttt{wchar.h}:日本語のようなマルチバイトの文字を扱うための型や関数が宣言・定義されている + \item \texttt{math.h}:三角関数や対数関数などの数学関数が宣言・定義されている +\end{itemize} + +\vspace{-0.5cm} +\defaultlistingstyle +\begin{lstlisting}[language=C,title={\texttt{\#include}マクロ},escapechar=\@] +#include <ヘッダーファイル.h> // 1) コンパイラ・システムが指定した + // ディレクトリ(フォルダパス)内でファイルを + // 探す。 + // Linuxならば@\texttt{/usr/include}@の場合が多い。 + // また、GCCでは引数に "-I <ディレクトリ>" を + // 渡すことで探索パスを実行時に追加できる。@\cite{gcc_man}@ + +#include "ヘッダーファイル.h" // 2) 書かれているソースコードと + // 同じディレクトリの中でファイルを探す。 + // 見つからなければ 1) と同じ挙動を取る。 +\end{lstlisting} + +\subsection{\texttt{main}関数} + +プログラムが呼び出される際に最初に実行される関数。 +この関数の戻り値は整数型であるが、これはプログラムの終了が正常であるかどうかを示めすものであり、Linuxでは0が正常終了、1が異常終了などである。 +この異常終了判定に使用される値はOSによって異なる。 +なので\texttt{stdlib.h}で提供される\texttt{exit()}関数や\texttt{EXIT\_SUCCESS, EXIT\_FAILURE}定数を使うのが望ましい。\cite{cppref_mainfunc} + +\begin{lstlisting}[language=C,title={\texttt{main}関数}] +int main(void) { // プログラム実行時に何も引数を渡す必要がない場合 + // 文 + + return 0; +} + +int main(int argc, char** argv) { // プログラム実行時に引数を渡す場合、argc は呼び出しプログラム名を含む引数の数、argv は呼び出しプログラム名を含む引数の値(文字列型)の配列である。 + // 文 + + return 0; +} +\end{lstlisting} + \subsection{\texttt{printf}関数} \texttt{printf}とはPrint Formatのことで、第一引数に書式(文字列)、第二以降の引数に表示したいデータ(変数、定数、リテラル)を羅列する。 -この関数は\texttt{stdio.h}を\lstinline[columns=fixed]{#include}(ソースコードに含める)ことで利用できる。\cite{cppref_printf} +この関数は\texttt{stdio.h}を\texttt{#include}することで利用できる。\cite{cppref_printf} \defaultlistingstyle \begin{lstlisting}[language=C,title={\texttt{printf}関数}] @@ -21,6 +73,8 @@ printf("<書式>", <データ1>, <データ2>, ...); 更に、書式には文字の表示を制御できる特殊文字(escape sequence)があり、一部の文字はソースコードと干渉するためそれらを使用しないと表示できない。特殊文字はバックスラッシュで始まり、ASCII文字1つが続く。 +\newpage + \begin{lstlisting}[language=C,title={特殊文字(一部)\cite{xiny}}] "\n" // 改行 "\\" // バックスラッシュ @@ -29,8 +83,6 @@ printf("<書式>", <データ1>, <データ2>, ...); "\'" // シングルクウォーテーションマーク \end{lstlisting} -\newpage - \subsection{変数宣言・定義}\label{var_decl_def} 変数はコンピュータのメモリ上にある値が入る箱のような物である。この箱には整数や小数値、文字などが入るが、メモリ上ではすべて1と0で表現されている。 @@ -54,3 +106,14 @@ a = 17; // 変数名 a に 17 という値を書き込む \begin{lstlisting}[language=C,title={C99以降の変数宣言・代入}] int a = 17; // 整数型の変数 a の宣言と同時に 17 という値を書き込む \end{lstlisting} + +型には一部として以下の物がある\cite{xiny}: + +\begin{itemize} + \item \texttt{int}:整数型 + \item \texttt{char}:文字型 + \item \texttt{float}:32ビット単精度浮遊少数型 + \item \texttt{double}:64ビット倍精度浮遊少数型 +\end{itemize} + +なお、一部の型が占有するバイト数はコンパイラやCPUによって異なるが、\texttt{x86\_64}の場合は\texttt{int}型が4バイト、\texttt{char}型が1バイトである。