The objective of this introductory lab is to familiarize you with the system environment for developing C programs under Linux on our lab machines.
Compile the code in directory v1/ by running
% gcc main.c
What are the sizes and protection settings
for owner, group, and other of main.c and a.out?
Use the command "ls" with option "-l" to determine the properties.
If the files are
accessible by group or other, please set the protection using
chmod so that they are protected per course policy.
Where is the file gcc which contains the executable code of our
C compiler located? Use the whereis command to find the
location (i.e., full pathname) of gcc. Where is the file whereis
located?
What happens when you run a.out?
Note: Per course policy
you must ensure that your work is protected from access by others.
For files belonging to you on CS's Linux, this means setting the
protection bits of group and other to not be readable, writable, executable.
The two components of the app gcc that we are primarily concerned with in CS 240 are the C compiler and preprocessor. As noted in class, the C preprocessor (CPP) is helper software that assists the C compiler by handling all statements starting with '#' such as #include <stdio.h> in v2/main.c. The CPP directive #include instructs CPP to locate the file stdio.h where the angle brackets indicate where to look for the file. On our lab machines running Linux, CPP will look for stdio.h in the directory /usr/include among other places that are part of gcc's configuration. Verify that stdio.h is located in the /usr/include directory. Copy stdio.h into your v2/ directory and rename the file myio.h using the cp command. Edit main.c and change stdio.h to myio.h. Compile main.c by running gcc. Explain what happens. Edit main.c and change the angle brackets surrounding myio.h to double quotes. Recompile main.c and explain what has happened. Run a.out and verify that it works correctly.
To actually run the machine code contained in the file a.out on the Linux
PC hardware in our labs, we utilize the help of another app, called a
shell, which acts primarily as a user interface.
There are various versions of shell apps used in Linux from the barebones
Bourne shell (/bin/sh) to more feature rich versions such as C shell (/bin/csh)
and Bourne Again Shell (/bin/bash). The core software structure of all shells
is the same, and we will code a simple version as part of an exercise for coding
client/server apps later in the course.
Determine the type of shell you are running on the Linux machine by
running the command /bin/ps.
Based on the discussion in class, explain what happens
when a.out is executed with the help of a shell app
$ a.out
at the shell prompt '$'. Note that shells are configurable so that
its prompt can be changed, for example, to '%', among many other variants.
Your explanation should include the roles shell, gcc, a.out, linker,
and loader.
We used code written by others, available as library functions, to output the result of multiplication to stdout in v2/main.c. As discussed in class, all functions that are used by other functions (in our case main()) must be declared in the file where the calling function is coded (in our case main.c). Since printf() used by main() is not declared by us in main.c, it must be declared in the header file stdio.h to satisfy C's requirement. The CPP statement #include <stdio.h> helped do that by locating the stdio.h in /usr/include and copying its content into main.c before actual compilation. Inspect stdio.h and find the function prototype of library function printf(). Note the line number where the declaration of printf() is located in stdio.h. Looking at the general shape of printf(), what is fundamentally different about it when compared to multiply2() in v6/main.c? What do we call C functions that follow the special structure of printf()? What other function have we used in the code examples that also follows printf()'s special structure?
Since v2/main.c only uses library function printf() but stdio.h contains function prototypes of many other library functions that we are not using, it stands to reason that declaring functions that are not used are ignored by gcc and does no harm. Hence a common C programming practice is to place multiple declarations in a single header file that is included in C source code files (i.e., .c files) even though only a subset of the declared functions are actually used. Make a copy of main.c and name it v2/mainv2.c. Modify mainv2.c so that the CPP directive #include is removed. In its place, declare the function printf() by copying its declaration from stdio.h. Compile mainv2.c and execute a.out to verify that it works correctly.
Application software coded in C can be viewed as a collection of functions whose execution starts at the designated function main() which may call other functions that, in turn, may make further function calls. This characterization is not completely accurate since main() itself contains statement "return" which indicates that main() itself is called by some other function. By default, gcc performs several tasks behind the scenes which includes inserting code of a function called _start() which calls main(). The return value of main(), by convention, is recommended to be 0 if main() has executed normally; some other value such as -1 if not. The role of _start() is systems related and beyond the scope of CS 240. In v2/main.c insert the return statement after the call to printf() with return value 0 or -1. Check that the program executes as before (i.e., without the return statement). Note that when a return statement is omitted, gcc inserts it on the programmer's behalf.
The version in v3/ makes a further enhancement such that the
two numbers to be multiplied are provided as input via standard input
which, by default, is the keyboard attached to a Linux PC in the labs.
On the surface and from the viewpoint of
a "user friendly" programming language, it
may seem that
scanf("%d %d", x, y)
is the natural format
to inform scanf() to read two integer values from keyboard
and store them in integer variables x and y.
How does gcc respond when you omit the ampersand & in front of x and y
when calling scanf()? Does it generate an error or warning? From a practical
perspective, how is a compilation warning different from a compilation error?
When you execute a.out you should see output to stdout
that reads "Segmentation fault" which is the most common run-bug you
will encounter in CS 240. Based on the discussion in class and providing
6 and 3 as input to scanf(), explain what may be causing the program
to fail. Is running a.out guaranteed to result in the run-time error
"Segmentation fault"? Discuss your reasoning.
Note: Bugs detected by gcc during compilation that result in gcc refusing to generate executable a.out are simple bugs. Difficulties arise when run-time bugs arise that cannot be detected at compilation time. The bulk of coding effort and time revolves around figuring out why run-time bugs occur. Proficiency at debugging is a skill acquired through practice and experience. There is no shortcut.
v4/ contains a floating point (i.e., real number) variation of v3/. In v5/ the code is made more modular by implementing the multiplication part as a separate function, multiply2(). Since multiply2() is essentially a one-liner, the separation doesn't buy much in terms of enhanced modularity. However, the principle is clear: if the tasks were more involved, putting the code in a separate function would make the design more modular and organized. multiply2() takes the values to be subtracted as its two arguments and returns the result to the calling function. Hence the caller is main() and the callee is multiply2(). When passing the two arguments to multiply2(), why do we not use ampersands, that is, invoke multiply2(&x,&y)? What happens if we do? Test by making a copy of v5/main.c, v5/mainv3.c, and compile by running gcc. Does the code change result in a compilation bug or run-time bug? Discuss your finding.
Can the code of v5/ be modified so that calling multiply2(&x,&y) will work correctly? Explain your reasoning including what code changes are logically needed for this to work. Implement the changes in v5/mainv4.c. Compile and run a.out to verify that the modified code works correctly.
Create a subdirectory v11/ under lab1/ and code a function middleval() which takes three integer arguments and returns the middle value called the median. For example, for input 10, 8, 15 the middle value is 10. For 5, 12, 12 the middle value is defined as 12. Do not use library functions but write your own C code from scratch. How elegant or efficient your code has no bearing on the points received for this problem. Those concerns will be addressed later in the course. Place middleval() in its own file v11/middleval.c. Verify that middleval() works correctly by calling it from a test function main() in v11/main.c and printing to stdout the result returned. Test middleval() with different arguments to increase confidence that your code works correctly. Make sure to annotate your code with comments that help the reader understand your code. In the real-world much of the time and effort in software engineering is spent testing. This includes run-time debugging since tests oftentimes fail due to bugs in the code. For complex bugs, an app does not crash nor show obvious signs of not working correctly. However, the results produced are sometimes wrong.
Create a subdirectory v12/ under lab1/ and code a function myfunction() which takes an integer arguments and returns an integer after performing some operations. What the operations are is up to you but for the following constraint: if the input is integer x and myfunction() returns integer y, then calling myfunction() with input y must return x. Explain your method for implementing myfunction() then implement myfunction() in v12/myfunction.c. Use your own driver function main() in v12/main.c to test and verify that your code works correctly.
The Bonus Problem is entirely optional. Bonus points count toward reaching 35% of the course grade contributed by lab assignments.
Electronic turn-in instructions:
i) For problems that require answering/explaining questions, submit a write-up as a pdf file called lab1.pdf. Place lab1.pdf in your directory lab1/. You can use your favorite editor subject to that it is able to export pdf files which many freeware editors do. Files submitted in any other format will not be graded. The TAs need to spend their time evaluating the content of your submission, not switching between editors or hunting down obscure document formats which wastes time and is in no one's interest.
ii) We will use turnin to manage lab assignment submissions. In the parent directory of lab1, run the command
turnin -c cs240 -p lab1 lab1
You can verify/list your submission by running: turnin -c cs240 -p lab1 -v. Please double-check that you submitted what you intended to submit.