============================================================ CBE Pt.8: Some Control Structures in C and Program Arguments [TOC] ============================================================ In C basically three types of control structures are provided: - Conditional statements (`if`, `if .. else` and `switch` statements) - Loop statements (`for`, `while ` and `do .. while` statements) - Unconditional statements (`return`, `break`, `continue` and `goto` statements) In the videos you see examples for some conditional statements (`if` and `if ... else`) and the loop statements. ---- VIDEO ------------------------------ https://www.youtube.com/embed/W6BqIyNhx4g ----------------------------------------- The `switch` and `goto` statement will not be covered in this session (but they will show up in the grammar). For all other statements (including those that were not used in the video) you find examples below. Example: Environment Variables ============================== Let's connect C programming with some Unix shell workout. The standard library provides the function `getenv` (declared in `stdlib.h`) to access the value of an exported shell variable. In this example it prints the value of the shell variables `USER` and `FOO`: Portable Method --------------- ---- CODE (file=session10/getenv/getenv.c) ------------------------------------- #include #include // for: char *getenv(const char *name); int main(int argc, char *argv[]) { // print all program arguments for (int i = 0; i < argc; ++i) { printf("argv[%d] = %s\n", i, argv[i]); } // print value of an environment variable printf("Variable %s = %s\n", "USER", getenv("USER")); printf("Variable %s = %s\n", "FOO", getenv("FOO")); } -------------------------------------------------------------------------------- With `echo $USER` you can print the value of variable `USER` in the Unix shell. On Unix this an exported variable (and therefore every child process gets a copy of it) and it is already initialized with your login name. Before we run our program we also define a shell variable `FOO` with some value, but we do not export it: ---- SHELL (path=session10/getenv/) -------------------------------------------- echo $USER FOO="This is foo" echo $FOO gcc -Wall -o getenv getenv.c getenv -------------------------------------------------------------------------------- Now we almost do the same but also export the variable: ---- SHELL (path=session10/getenv/) -------------------------------------------- echo $USER export FOO="This is foo" echo $FOO gcc -Wall -o getenv getenv.c getenv -------------------------------------------------------------------------------- You also can first define a variable without exporting it, and afterwards export it: ---- SHELL (path=session10/getenv/) -------------------------------------------- echo $USER FOO="This is foo" export FOO echo $FOO gcc -Wall -o getenv getenv.c getenv -------------------------------------------------------------------------------- Some *Not* Portable Method to Access Environment Variables ---------------------------------------------------------- Most implementations of the C library support that `main` can get a third parameter (here called `envp`) which is an array with pointers to a `char`. The size of the array is not provided through an additional parameter. Instead the end of the array is marked with a null pointer. The C library used by `gcc` on `theon` does support this an this program prints all environment variables: ---- CODE (file=session10/envp/envp.c) ----------------------------------------- #include extern int x[]; int main(int argc, char *argv[], char *envp[]) { for (int i = 0; i < argc; ++i) { printf("argv[%d] = %s\n", i, argv[i]); } for (int i = 0; envp[i]; ++i) { printf("envp[%d]: %s\n", i, envp[i]); } } -------------------------------------------------------------------------------- The program uses a loop to check first if `envp[0]` is not a null pointer. If that is the case it points to a definition of a variable (of form `VARNAME=VALUE`). It then continues with the next array element until a null pointer is found: ---- SHELL (path=session10/envp/,fold) ----------------------------------------- gcc -Wall -o envp envp.c envp -------------------------------------------------------------------------------- Food for Thought: Counting Backwards with a For Loop ==================================================== Here is a perfectly fine program which uses a for-loop to print the integer numbers from `n - 1` down to `0`: ---- CODE (file=session10/hpc0_cprog_page4/src/xcountdown.c) ------------------- #include int main() { int n = 5; for (int i = n - 1; i >= 0; --i) { printf("i = %d\n", i); } } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xcountdown ./build_gcc/xcountdown -------------------------------------------------------------------------------- So what is the problem? For some reason you are supposed to used an unsigned integer type (e.g. `unsigned`) for the variables `i` and `n`. Well, this does not seem to be too hard. You simple change the code into the following: ---- CODE (file=session10/hpc0_cprog_page4/src/xcountdown_unsigned.c) ---------- #include int main() { unsigned n = 5; for (unsigned i = n - 1; i >= 0; --i) { printf("i = %d\n", i); } } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- There actually seems to be no problem. Until you run the program ... you got stuck in an infinite loop. ---- BOX ----------------------------------------------------------------------- - Run the program - You can kill the process with `Control-c` -------------------------------------------------------------------------------- - Explain why the condition `i >= 0` is always true. For example, explain how `ucc` checks a condition of the form `a >= b` in general if `a` and `b` are unsigned integers. Write your answer in a file `that_s_why.txt`. - Fix the problem. Feel free to search the web. At least on __stackoverflow__ you can find an answer with a typical pattern for dealing with this case. Also some pattern I don't like that much. But of course whatever working pattern you come up with is fine. It's just important that you are aware of the problem and know how to deal with it. Some hint about where this might be needed: In numerical linear algebra it is a common problem to solve a system of linear equations where the coefficient matrix is upper triangular. The method for solving this problem is called __back substitution__. The algorithm for that is actually quite simple. And for implementing it in C you just need a loop that counts from `n-1` down to `0`. And because the integers refer here to matrix or vector indices that can not be negative it perfectly makes sense that this should work for unsigned integers. We will discuss the problem in class and I will show my pattern for dealing with this problem. :links: stackoverflow -> https://stackoverflow.com back substitution -> https://en.wikipedia.org/wiki/Triangular_matrix#Forward_and_back_substitution Overview: Statements in C ========================= Currently the ULM compiler does not support all kind of statements: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{statement}\rangle & \to & \langle\text{expression-statement}\rangle\; \textbf{;}\; \\ & \to & \langle\text{compound-statement}\rangle \\ & \to & \langle\text{if-statement}\rangle \\ & \to & \langle\text{while-loop}\rangle \\ & \to & \langle\text{do-while-loop}\rangle \\ & \to & \langle\text{for-loop}\rangle \\ & \to & \langle\text{break-statement}\rangle\; \\ & \to & \langle\text{continue-statement}\rangle\; \\ & \to & \langle\text{return-statement}\rangle\; \\ & \to & \langle\text{switch-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ & \to & \langle\text{goto-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ & \to & \langle\text{labeled-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ \end{array} -------------------------------------------------------------------------------- ---- SHELL (path=session10/,hide) ---------------------------------------------- rm -rf hpc0_cprog_page4 cp -r -P /home/numerik/pub/cprog hpc0_cprog_page4 -------------------------------------------------------------------------------- expression-statement ==================== The simplest kind of statement was pretty much covered __Session 10 (Expressions in C)__. But maybe it suprises you that the expression is actualy optional: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{expression-statement}\rangle & \to & \langle\text{expression}\rangle_\text{opt}\; \textbf{;}\; \\ \end{array} -------------------------------------------------------------------------------- So this is perfectly legal code with lots of empty statements used for some kind of ASCII art in the source code: ---- CODE (file=session10/hpc0_cprog_page4/src/xempty_stmts.c) ----------------- #include int main() { ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; puts("hello, world!") ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xempty_stmts ./build_gcc/xempty_stmts -------------------------------------------------------------------------------- A more useful application can be empty statements in a loop (see below). For example: ---- CODE (file=session10/hpc0_cprog_page4/src/xpress_return.c) ---------------- #include int main() { puts("Press 'enter' to continue"); while (getchar() != '\n'); puts("So you found 'enter' on your keyboard"); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- Try for yourself. And of course it is always fun to write some confusing code: ---- CODE (file=session10/hpc0_cprog_page4/src/xcounting.c) -------------------- #include int main() { for (int i = 0; printf("i = %d\n", i), i < 10; ++i); } -------------------------------------------------------------------------------- Here the eductional reason for showing this is actually the conditional expression of the for-loop ---- CODE (type=c) ------------------------------------------------------------- printf("i = %d\n", i), i < 1 -------------------------------------------------------------------------------- The value of an expression list is the value of the last expression item, i.e. `i < 1`. So it does not matter that the function call expression `printf(...)` does not have an integer type. This expression list evaluates to `i < 10` but as a side-effect also `printf` gets called. So we have a proper for-loop. ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xcounting ./build_gcc/xcounting -------------------------------------------------------------------------------- :links: Session 10 \(Expressions in C\) -> doc:session08/page04 compound-statement ================== Compund statements begin with a open parenthesis constain some sequence of declarations and statements and end with a closed parenthesis: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{compound-statement} & \to & \textbf{\{}\; \langle\text{declaration-or-statement-list} \rangle_\text{opt} \; \textbf{\}} \\ \langle\text{declaration-or-statement-list}\rangle & \to & \langle\text{declaration-or-statement}\rangle \\ & \to & \langle\text{declaration-or-statement}\rangle\; \langle\text{declaration-or-statement-list}\rangle \\ \langle\text{declaration-or-statement} & \to & \langle\text{declaration}\rangle\; \\ & \to & \langle\text{statement}\rangle \\ \end{array} -------------------------------------------------------------------------------- Every function definition has at least one compound statement. If you declare a variable inside a compound statement it is a local variable (unless it has a `static` qualifier, but more about that later). Scope or visibility of variables declared in a compound statement ----------------------------------------------------------------- The scope (or visibility) of a variable declared in a compound statement is limited to this block. So in this example the compiler would complain in the line with the second `printf` that no variable `i` was declared: ---- CODE (type=c) ------------------------------------------------------------- #include int main() { { int i = 42; // only visible in this compund statement printf("i = %d\n", i); // ok, would print 'i = 42' } printf("i = %d\n", i); } -------------------------------------------------------------------------------- Shadowing previous declarations ------------------------------- It is legal and in many cases helpful that you can use identifiers in a declaration within a compound statement that was already used before in a declaration. In this case the previous declaration gets shadowed: ---- CODE (file=session10/hpc0_cprog_page4/src/xshadow.c) ---------------------- #include int i = 42; // global variable i int main() { int i = 43; // new local variable i printf("1) i = %d\n", i); // prints 'i = 43' not 'i = 42' { int i = 44; // new local variable i printf("2) i = %d\n", i); // prints 'i = 44' not 'i = 43' } } -------------------------------------------------------------------------------- Once a previous declaration is shadowed there is no way back to refer to the previous declaration (in other languages you sometimes can refer to a global scoped variable). ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xshadow ./build_gcc/xshadow -------------------------------------------------------------------------------- When does the shadowing begin? ------------------------------ Short answer: Once the declaration gets parsed. Since C99 within a compound statement you can mix declarations and statements in what ever order you might think is helpful. Before it was required to do all the declarations first and then the statements. So you can have statements in the compound statement that still refer to the previously (on the outside) declared variable while others refer to the locally declared variable: ---- CODE (file=session10/hpc0_cprog_page4/src/xshadow2.c) --------------------- #include int i = 42; // global variable i int main() { printf("1) i = %d\n", i); // prints '?????' int i = 43; // new local variable i // shadows printf("2) i = %d\n", i); // prints 'i = 43' { printf("3) i = %d\n", i); // prints '?????' int i = 44; // new local variable i printf("4) i = %d\n", i); // prints 'i = 44' } } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- The compiler parses the code line-by-line. The compiler creates a new entry in its symbol table whenever a declaration is encountered. This symbol table is hierarchical in the following sense: - The compiler initially just has one global symbol table (for all global variables), - when a compound statement is entered a new local symbol table is created which has the previous symbol table as its parent. If an expression refers to a variable the compiler does a loop-up in the current symbol table. If the identifier was not found it performs a look-up in the parent symbol table. This is done recursively until the identifier is either found or (and this would cause an "identifier not declared" error) not found. Hence, you see the following output: ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xshadow2 ./build_gcc/xshadow2 -------------------------------------------------------------------------------- The output marked with `2)` prints the value of the variable `i` found in the global symbol table. That's because `i` was not found in the current symbol table. The output marked with `3)` comes from a statement that was parsed after the local symbol table already contained an identifier `i`. if-statement ============ An if-statement can have an optional else-branch: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{if-statement}\rangle & \to & \textbf{if}\; \textbf{(}\; \langle\text{expression}\rangle\; \textbf{)}\; \langle\text{statement}\rangle\; \\ & \to & \textbf{if}\; \textbf{(}\; \langle\text{expression}\rangle\; \textbf{)}\; \langle\text{statement}\rangle\; \textbf{else}\; \langle\text{statement}\rangle\; \\ \end{array} -------------------------------------------------------------------------------- An expression is considered to be true if it evaluates to a non-zero value. Personally I prefer to always use a compound statement even if an expression statement would do. So instead of ---- CODE (type=c) ------------------------------------------------------------- if (i > 42) puts("i is greater than 42"); // expression statement else puts("i is not greater than 42"); // expression statement -------------------------------------------------------------------------------- I would write ---- CODE (type=c) ------------------------------------------------------------- if (i > 42) { puts("i is greater than 42"); // inside of a compound statement } else { puts("i is not greater than 42"); // expression statement } -------------------------------------------------------------------------------- However, this is just for convenience (in case I want to add another statement to the then-branch or else-branch later). Note that this also avoids the dangling else Problem that will be shown below. Flow chart for an if-statement without an else-branch ----------------------------------------------------- Consider this example without an else-branch ---- CODE (type=c) ----------------------------------------------------------- puts("A"); if (i > 42) { puts("B"); } puts("C"); ------------------------------------------------------------------------------ The assembly code generated by the ULM compiler can be indicated by the following flow chart: ---- TIKZ -------------- \begin{tikzpicture} \input{flowchart.tex} \SetMargin{-1}{1}{0}{3} \renewcommand\BoxWidth { 5 } \PutStatement{0}{puts("A");} \PutJump{1}{i <= 42} \PutStatement{2}{puts("B");} \PutJump{3}{} \PutStatement{4}{puts("C");} \AddPath{0}{1} \AddPath{1}{2} \AddCondJumpPath{1}{4} \AddPath{2}{3} \AddJumpPathLeft{3}{4} \end{tikzpicture} ------------------------ Note that the condition `i > 42` was negated to `i <= 42`. Flow chart for an if-statement with an else-branch -------------------------------------------------- Let's modify the previous example so that we have an else-branch ---- CODE (type=c) ----------------------------------------------------------- puts("A"); if (i > 42) { puts("B"); } else { puts("C"); } puts("D"); ------------------------------------------------------------------------------ The assembly code generated by the ULM compiler can be indicated by the following flow chart on the left-hand side (the flow chart on the right-hand side would be a legal alternative): + ---- TIKZ -------------- \begin{tikzpicture} \input{flowchart.tex} \SetMargin{-10}{1}{3}{4} \renewcommand\BoxWidth { 5 } \PutStatement{0}{puts("A");} \PutJump{1}{i <= 42} \PutStatement{2}{puts("B");} \PutJump{3}{} \PutStatement{4}{puts("C");} \PutStatement{5}{puts("D");} \AddPath{0}{1} \AddPath{1}{2} \AddCondJumpPath{1}{4} \AddPath{2}{3} \AddPath{4}{5} \AddJumpPathLeft{3}{5} \end{tikzpicture} ------------------------ + ---- TIKZ -------------- \begin{tikzpicture} \input{flowchart.tex} \SetMargin{-10}{1}{3}{4} \renewcommand\BoxWidth { 5 } \PutStatement{0}{puts("A");} \PutJump{1}{i > 42} \PutStatement{2}{puts("C");} \PutJump{3}{} \PutStatement{4}{puts("B");} \PutStatement{5}{puts("D");} \AddPath{0}{1} \AddPath{1}{2} \AddCondJumpPath{1}{4} \AddPath{2}{3} \AddPath{4}{5} \AddJumpPathLeft{3}{5} \end{tikzpicture} ------------------------ Note that for the variant on the left-hand side the condition has to be negated, but not for the variant on the right-hand side. Consequently the order in with the then-branch and else-branch occurs in the generated code is interchanged. __Dangling else problem__ ------------------------- Consider the following two code snippets and be aware that the compiler does not care about how you indent your code: + ---- CODE (type=c) --------------------------------------------------------- if (i >= 42) if (i > 42) printf("%d is greater than 42", %d); else printf("%d is less than 42", i); ---------------------------------------------------------------------------- + ---- CODE (type=c) --------------------------------------------------------- if (i >= 42) if (i > 42) printf("%d is greater than 42", i); else printf("%d is equal to 42", i); ---------------------------------------------------------------------------- So one of this variants should not behave as the programmer seems to expect. Let's test this with a small test program: ---- CODE (file=session10/hpc0_cprog_page4/src/xdangling_else.c) --------------- #include void variant_left(int i) { puts("I am the code from the left-hand side"); puts("-------------------------------------"); if (i >= 42) if (i > 42) printf("%d is greater than 42", i); else printf("%d is less than 42", i); puts("\n"); } void variant_right(int i) { puts("I am the code from the right-hand side"); puts("--------------------------------------"); if (i >= 42) if (i > 42) printf("%d is greater than 42", i); else printf("%d is equal to 42", i); puts("\n"); } void test(int i) { printf("Test for i = %d\n", i); printf("===============\n\n"); variant_left(i); variant_right(i); } int main() { test(41); test(42); test(43); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xdangling_else ./build_gcc/xdangling_else -------------------------------------------------------------------------------- From looking merely at the grammar the question "to which if does the dangling else blong" can not be answered. The specification for the correct behaviour is buried in the C standard which states "it belongs to the previous if". Using compound statements instead of simple expression statements the meaning of the above code can be made more expressive as follows (the indentation was intentionally not adapted): + ---- CODE (type=c) --------------------------------------------------------- // how the dangling else ... if (i >= 42) { if (i > 42) { printf("%d is greater than 42", %d); } else { printf("%d is less than 42", i); } } ---------------------------------------------------------------------------- + ---- CODE (type=c) --------------------------------------------------------- // ... gets handled in C if (i >= 42) { if (i > 42) { printf("%d is greater than 42", i); } else { printf("%d is equal to 42", i); } } ---------------------------------------------------------------------------- :links: Dangling else problem -> https://en.wikipedia.org/wiki/Dangling_else do-while-loop ============= ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{do-while-loop}\rangle & \to & \textbf{do}\; \langle\text{statement}\rangle\; \textbf{while}\; \textbf{(}\; \langle\text{expression}\rangle\; \textbf{)}\; \textbf{;}\; \\ \end{array} -------------------------------------------------------------------------------- The statement of a do-while-loop gets executed at least once. Then the expression gets evaluated, and if it evaluates to a non-zero value the statement gets executed again. It is a good exercise to draw a flow chart for the do-while-loop to see how it differs from a while-loop. For checking your flow chart consider some example programs and see if your flow chart describes the output. The statement gets executed at least once ----------------------------------------- In this example the condition is always false (because a hard coded `false` which is a zero literal is used). Due to my personal coding style I would use a compound statement but of course you also can use an expression statement like here: ---- CODE (file=session10/hpc0_cprog_page4/src/xwhile1.c) ---------------------- #include #include int main() { do puts("do-while-statement executed\n"); while (false); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xwhile1 ./build_gcc/xwhile1 -------------------------------------------------------------------------------- Typical use: Repeat the statement until a condition is met ---------------------------------------------------------- In this example the statement gets repeated until `getchar()` could read an upper case latter (`'A'`, ... `'Z'`) from standard input or returned `EOF` (end of file which you can type in with `CONTROL-D`). The condition for the loop is ---- CODE (type=c) ------------------------------------------------------------- (ch = getchar()) != EOF && !(ch >= 'A' && ch <= 'Z') -------------------------------------------------------------------------------- and intensionally a bit more complex. If necessary, review parts of the previous page to convince yourself that this is actually an expression that evaluates to `true` if the condition is met. Furthermore, the side effect is here that variable `ch` either has the value `EOF` or `'A'`. ---- CODE (file=session10/hpc0_cprog_page4/src/xwhile2.c) ---------------------- #include #include int main() { int ch; do printf("Give me some upper case letter!\n"); while ((ch = getchar()) != EOF && !(ch >= 'A' && ch <= 'Z')); if (ch == EOF) { puts("Did not get an upper case letter before end-of-file was reached"); } else { printf("I got an '%c'\n", ch); } } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- For testing you either can use the program interactively (i.e. run it and type some characters) or use some files as input. For example: - With a file like this ---- CODE (file=session10/hpc0_cprog_page4/file_with_uppercase) -------------- abC BAGh ------------------------------------------------------------------------------ you get ---- SHELL (path=session10/hpc0_cprog_page4) --------------------------------- ./build_ucc/xwhile2 < file_with_uppercase ./build_gcc/xwhile2 < file_with_uppercase ------------------------------------------------------------------------------ - And with this file ---- CODE (file=session10/hpc0_cprog_page4/file_without_uppercase) ----------- abc bgh ------------------------------------------------------------------------------ you get ---- SHELL (path=session10/hpc0_cprog_page4) --------------------------------- ./build_ucc/xwhile2 < file_without_uppercase ./build_gcc/xwhile2 < file_without_uppercase ------------------------------------------------------------------------------ while-loop ========== You saw quite a few examples already for using a while loop in C. So you are probably not surprised by its grammar: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{while-loop}\rangle & \to & \textbf{while}\; \textbf{(}\; \langle\text{expression}\rangle\; \textbf{)}\; \langle\text{statement}\rangle\; \end{array} -------------------------------------------------------------------------------- Flow chart for understanding the semantics ------------------------------------------ Let's consider this example for explaining the semantic: ---- CODE (file=session10/hpc0_cprog_page4/src/xwhile.c) ----------------------- #include int main() { int i = 0; while (i < 5) { printf("i = %d\n", i); ++i; } printf("after the loop: i = %d\n", i); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xwhile ./build_gcc/xwhile -------------------------------------------------------------------------------- The are two possible flow-charts for describing the logic (i.e. semantic) of the while loop. This also the two principle variants for generating assembly code: + ---- TIKZ -------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} %\SetMargin{1}{1}{0}{6.1} \renewcommand\BoxWidth { 7 } \PutStatement{0}{i = 0;} \PutJump{1}{i >= 5} \PutStatement{2}{printf("i = \%d\textbackslash n", i);} \PutStatement{3}{++i;} \PutJump{4}{} \PutStatement{5}{printf("after the loop: i = \%d\textbackslash n", i);} \AddPath{0}{1} \AddPath{1}{2} \AddCondJumpPath{1}{5} \AddPath{2}{3} \AddPath{3}{4} \AddJumpPathLeft{4}{1} \end{tikzpicture} ------------------------------------------------------------------------------ + ---- TIKZ -------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} %\SetMargin{1}{1}{0}{5.1} \renewcommand\BoxWidth { 7 } \PutStatement{0}{i = 0;} \PutJump{1}{} \PutStatement{2}{printf("i = \%d\textbackslash n", i);} \PutStatement{3}{++i;} \PutJump{4}{i < 5} \PutStatement{5}{printf("after the loop: i = \%d\textbackslash n", i);} \AddPath{0}{1} \AddJumpPathLeft{1}{4} \AddPath{2}{3} \AddPath{3}{4} \AddCondJumpPath{4}{2} \AddPath{4}{5} \end{tikzpicture} ------------------------------------------------------------------------------ Exercise ~~~~~~~ Look into the assembler code generated by `ucc` to figure out which of the flow chart is visualizing its code generation for while loops. Some example for bad taste -------------------------- Just as a remainder for the meaning of the postfix increment operator consider this example: ---- CODE (file=session10/hpc0_cprog_page4/src/xwhile2.c) ---------------------- #include int main() { int i = 0; // with this code you sooner or later will shot your own leg ... while (i < 5) printf("i = %d\n", i++); printf("after the loop: i = %d\n", i); } -------------------------------------------------------------------------------- Doing the increment within the function call is certainly legit. However, it is unreadable code because you easily might oversee it in that place. This in turn means that your code is error prone to modifications. And this in turn means you sooner or later get trouble from that code. ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xwhile2 ./build_gcc/xwhile2 -------------------------------------------------------------------------------- Actually some note here ---- BOX ----------------------------------------------------------------------- Due to my coding style I only write `i++` if writing `++i` would not have the same effect. This means in my code I rarely see any use of `i++` or `i--`. It is actually that rare that my subconsciousness triggers alarm and puts me in fight or flight mode when there is `++i` or `--i` in the same room. This means I certainly would never oversee the `i++` in the `printf("i = %d\n", i++)` expression. -------------------------------------------------------------------------------- Still, I would not use this code. However, in the quiz you will see that in a few cases C might kind of force you to use the postfix operand. So usage of `i++` or `i--` means that I am applying some established pattern in this piece of code. for-loop ======== The grammar of a for-loop is formally ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{for-loop}\rangle & \to & \textbf{for}\; \textbf{(}\; \langle\text{initial-clause}\rangle_\text{opt}\; \textbf{;}\; \langle\text{expression}\rangle_\text{opt}\; \textbf{;}\; \langle\text{expression}\rangle_\text{opt}\; \textbf{)}\; \langle\text{statement}\rangle\; \\ \langle\text{initial-clause}\rangle\; & \to & \langle\text{expression}\rangle\; \\ & \to & \langle\text{declaration}\rangle_\text{since C99}\; \\ \end{array} -------------------------------------------------------------------------------- For describing the semantics let's just give the expressions and the statement distinct (and meaningful) names: ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{for-loop}\rangle & \to & \textbf{for}\; \textbf{(}\; \langle\text{initial-clause}\rangle_\text{opt}\; \textbf{;}\; \langle\text{condition}\rangle_\text{opt}\; \textbf{;}\; \langle\text{update}\rangle_\text{opt}\; \textbf{)}\; \langle\text{loop-body}\rangle\; \\ \langle\text{initial-clause}\rangle\; & \to & \langle\text{expression}\rangle\; \\ & \to & \langle\text{declaration}\rangle_\text{since C99}\; \\ \langle\text{condition}\rangle\; & \to & \langle\text{expression}\rangle\; \\ \langle\text{update}\rangle\; & \to & \langle\text{expression}\rangle\; \\ \langle\text{loop-body}\rangle\; & \to & \langle\text{statement}\rangle\; \\ \end{array} -------------------------------------------------------------------------------- Then the semantic of a for-loop can be described by the following flow-charts: + ---- TIKZ -------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \SetMargin{1}{1}{0}{5.1} \renewcommand\BoxWidth { 7 } \PutStatement{0}{initial-clause} \PutJump{1}{! condition} \PutStatement{2}{loop-body} \PutStatement{3}{update} \PutJump{4}{} \PutStatement{5}{} \AddPath{0}{1} \AddPath{1}{2} \AddCondJumpPath{1}{5} \AddPath{2}{3} \AddPath{3}{4} \AddJumpPathLeft{4}{1} \end{tikzpicture} ------------------------------------------------------------------------------ + ---- TIKZ -------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \SetMargin{1}{1}{0}{1.1} \renewcommand\BoxWidth { 7 } \PutStatement{0}{initial-clause} \PutJump{1}{} \PutStatement{2}{loop-body} \PutStatement{3}{update} \PutJump{4}{condition} \PutStatement{5}{} \AddPath{0}{1} \AddJumpPathLeft{1}{4} \AddPath{2}{3} \AddPath{3}{4} \AddCondJumpPath{4}{2} \AddPath{4}{5} \end{tikzpicture} ------------------------------------------------------------------------------ The initial-clause is evaluated exactly once before the condition gets evaluated. If the condition is true the loop-body is executed. Each time the loop-body was executed the update-expression gets evaluated. After the update the condition of the loop is checked again. Some common use-pattern is therefor: - Initialize some counter with the initial-clause, - Check if the counter is within some valid rang in the condition, and - increment/decrement the counter in the update-expression. Before C99 the initial-clause could only be an expression. Since C99 it can also be an declaration, like in this code snippets: - for-loop with an expression statement as loop-body: ---- CODE (type=c) ----------------------------------------------------------- for (int i = 0; i < n; ++i) /* i declared in the initial clause is only visible within this statement */ ; ------------------------------------------------------------------------------ - for-loop with a compound statement as loop-body: ---- CODE (type=c) ----------------------------------------------------------- for (int i = 0; i < n; ++i) { /* i declared in the initial clause is only visible within this compound statement */ } ------------------------------------------------------------------------------ Leaving the initial-clause, condition or update empty ----------------------------------------------------- Expressions can be empty (see page 3). Hence, the grammar allows that the initial-clause, condition and update are empty expressions. In case the initial-clause or update the compiler just does not generate any instructions for these parts. If the expression for the condition is empty then the condition is always true. Hence this is a perfectly legal infinite-loop: ---- CODE (type=c) ------------------------------------------------------------- for (;;) puts("I will print this forever"); -------------------------------------------------------------------------------- There is also no requirement that a counter is incremented with the update expression. For counting from $0$ to $n-1$ this code might be unusual but legal: ---- CODE (file=session10/hpc0_cprog_page4/src/xfor.c) ------------------------- #include #include int main() { int n = 5; for (int i = -1; ++i < n;) { printf("i= %d\n", i); } } -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4, fold) ----------------------------- make -------------------------------------------------------------------------------- ---- SHELL (path=session10/hpc0_cprog_page4) ----------------------------------- ./build_ucc/xfor ./build_gcc/xfor -------------------------------------------------------------------------------- break-statement and continue-statement ====================================== ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{break-statement}\rangle & \to & \textbf{break}\; \textbf{;}\; \\ \langle\text{continue-statement}\rangle & \to & \textbf{continue}\; \textbf{;}\; \\ \end{array} -------------------------------------------------------------------------------- Break and continue statements are allowed within a loop (for-loop, while-loop or do-while-loop): - a break-statement causes a jump to the end of the loop, i.e. the first instruction that follows the loop. - continue-statement causes a jump to the first instruction of the loop condition which checks whether a loop-body gets executed another time. return-statement ================ ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{return-statement}\rangle & \to & \textbf{return}\; \langle\text{expression}\rangle_\text{opt}\; \textbf{;}\; \\ \end{array} -------------------------------------------------------------------------------- A return statements jumps to the end of the current function. Statements not supported by `ucc` ================================= Currently not supported by `ucc` are switch and goto statements and everything around these, i.e. ---- LATEX --------------------------------------------------------------------- \begin{array}{rcl} \langle\text{statement}\rangle & \to & \langle\text{switch-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ & \to & \langle\text{goto-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ & \to & \langle\text{labeled-statement}\rangle_\text{not supported by ucc}\; \textbf{;}\; \\ \langle\text{switch-statement}\rangle & \to & \textbf{switch}\; \textbf{(}\; \langle\text{expression}\rangle\; \textbf{)}\; \langle\text{statement}\rangle \\ \langle\text{goto-statement}\rangle & \to & \textbf{goto}\; \langle\text{named-label}\rangle\; \textbf{;}\; \\ \langle\text{labeled-statement}\rangle & \to & \langle\text{label}\rangle\; \textbf{:}\; \langle\text{statement}\rangle\; \\ \langle\text{label}\rangle & \to & \langle\text{named-label}\rangle\; \textbf{:}\; \langle\text{statement}\rangle\; \\ & \to & \langle\text{case-label}\rangle\; \textbf{:}\; \langle\text{statement}\rangle\; \\ & \to & \langle\text{default-label}\rangle\; \textbf{:}\; \langle\text{statement}\rangle\; \\ \langle\text{named-label}\rangle & \to & \langle\text{identifier}\rangle\; \\ \langle\text{case-label}\rangle & \to & \textbf{case}\; \langle\text{constant-expression}\rangle\; \\ \langle\text{default-label}\rangle & \to & \textbf{default}\; \\ \end{array} -------------------------------------------------------------------------------- Consider this as kind of a to-do list.