• C Data Types
  • C Operators
  • C Input and Output
  • C Control Flow
  • C Functions
  • C Preprocessors
  • C File Handling
  • C Cheatsheet
  • C Interview Questions
  • Solve Coding Problems
  • getch() function in C with Examples
  • C Program to Print Armstrong Numbers Between 1 to 1000
  • rand() in C
  • Else without IF and L-Value Required Error in C
  • C program to check if a given string is Keyword or not
  • Reverse String in C
  • Discrete Fourier Transform and its Inverse using C
  • time.h header file in C with Examples
  • How to use gotoxy() in codeblocks?
  • Program For Newton’s Forward Interpolation
  • How to avoid Structure Padding in C?
  • C Program to Reverse a String Using Recursion
  • C Program To Remove Duplicates From Sorted Array
  • C Program to Find Determinant of a Matrix
  • Concatenating Two Strings in C
  • C Program to Convert Decimal to Octal
  • Convert Binary to Decimal in C
  • C Program to Add N Distances Given in inch-feet System using Structures

C Exercises – Practice Questions with Solutions for C Programming

The best way to learn C programming language is by hands-on practice. This C Exercise page contains the top 30 C exercise questions with solutions that are designed for both beginners and advanced programmers. It covers all major concepts like arrays, pointers, for-loop, and many more.

C-Exercises

So, Keep it Up! Solve topic-wise C exercise questions to strengthen your weak topics.

C Programming Exercises

The following are the top 30 programming exercises with solutions to help you practice online and improve your coding efficiency in the C language. You can solve these questions online in GeeksforGeeks IDE.

Q1: Write a Program to Print “Hello World!” on the Console.

In this problem, you have to write a simple program that prints “Hello World!” on the console screen.

For Example,

Click here to view the solution.

Q2: write a program to find the sum of two numbers entered by the user..

In this problem, you have to write a program that adds two numbers and prints their sum on the console screen.

Q3: Write a Program to find the size of int, float, double, and char.

In this problem, you have to write a program to print the size of the variable.

Q4: Write a Program to Swap the values of two variables.

In this problem, you have to write a program that swaps the values of two variables that are entered by the user.

Swap-two-Numbers

Swap two numbers

Q5: Write a Program to calculate Compound Interest.

In this problem, you have to write a program that takes principal, time, and rate as user input and calculates the compound interest.

Q6: Write a Program to check if the given number is Even or Odd.

In this problem, you have to write a program to check whether the given number is even or odd.

Q7: Write a Program to find the largest number among three numbers.

In this problem, you have to write a program to take three numbers from the user as input and print the largest number among them.

Q8: Write a Program to make a simple calculator.

In this problem, you have to write a program to make a simple calculator that accepts two operands and an operator to perform the calculation and prints the result.

Q9: Write a Program to find the factorial of a given number.

In this problem, you have to write a program to calculate the factorial (product of all the natural numbers less than or equal to the given number n) of a number entered by the user.

Q10: Write a Program to Convert Binary to Decimal.

In this problem, you have to write a program to convert the given binary number entered by the user into an equivalent decimal number.

Q11: Write a Program to print the Fibonacci series using recursion.

In this problem, you have to write a program to print the Fibonacci series(the sequence where each number is the sum of the previous two numbers of the sequence) till the number entered by the user using recursion.

FIBONACCI-SERIES

Fibonacci Series

Q12: Write a Program to Calculate the Sum of Natural Numbers using recursion.

In this problem, you have to write a program to calculate the sum of natural numbers up to a given number n.

Q13: Write a Program to find the maximum and minimum of an Array.

In this problem, you have to write a program to find the maximum and the minimum element of the array of size N given by the user.

Q14: Write a Program to Reverse an Array.

In this problem, you have to write a program to reverse an array of size n entered by the user. Reversing an array means changing the order of elements so that the first element becomes the last element and the second element becomes the second last element and so on.

reverseArray

Reverse an array

Q15: Write a Program to rotate the array to the left.

In this problem, you have to write a program that takes an array arr[] of size N from the user and rotates the array to the left (counter-clockwise direction) by D steps, where D is a positive integer. 

Q16: Write a Program to remove duplicates from the Sorted array.

In this problem, you have to write a program that takes a sorted array arr[] of size N from the user and removes the duplicate elements from the array.

Q17: Write a Program to search elements in an array (using Binary Search).

In this problem, you have to write a program that takes an array arr[] of size N and a target value to be searched by the user. Search the target value using binary search if the target value is found print its index else print ‘element is not present in array ‘.

Q18: Write a Program to reverse a linked list.

In this problem, you have to write a program that takes a pointer to the head node of a linked list, you have to reverse the linked list and print the reversed linked list.

Q18: Write a Program to create a dynamic array in C.

In this problem, you have to write a program to create an array of size n dynamically then take n elements of an array one by one by the user. Print the array elements.

Q19: Write a Program to find the Transpose of a Matrix.

In this problem, you have to write a program to find the transpose of a matrix for a given matrix A with dimensions m x n and print the transposed matrix. The transpose of a matrix is formed by interchanging its rows with columns.

Q20: Write a Program to concatenate two strings.

In this problem, you have to write a program to read two strings str1 and str2 entered by the user and concatenate these two strings. Print the concatenated string.

Q21: Write a Program to check if the given string is a palindrome string or not.

In this problem, you have to write a program to read a string str entered by the user and check whether the string is palindrome or not. If the str is palindrome print ‘str is a palindrome’ else print ‘str is not a palindrome’. A string is said to be palindrome if the reverse of the string is the same as the string.

Q22: Write a program to print the first letter of each word.

In this problem, you have to write a simple program to read a string str entered by the user and print the first letter of each word in a string.

Q23: Write a program to reverse a string using recursion

In this problem, you have to write a program to read a string str entered by the user, and reverse that string means changing the order of characters in the string so that the last character becomes the first character of the string using recursion. 

Reverse-a-String

reverse a string

Q24: Write a program to Print Half half-pyramid pattern.

In this problem, you have to write a simple program to read the number of rows (n) entered by the user and print the half-pyramid pattern of numbers. Half pyramid pattern looks like a right-angle triangle of numbers having a hypotenuse on the right side.

Q25: Write a program to print Pascal’s triangle pattern.

In this problem, you have to write a simple program to read the number of rows (n) entered by the user and print Pascal’s triangle pattern. Pascal’s Triangle is a pattern in which the first row has a single number 1 all rows begin and end with the number 1. The numbers in between are obtained by adding the two numbers directly above them in the previous row.

pascal-triangle

Pascal’s Triangle

Q26: Write a program to sort an array using Insertion Sort.

In this problem, you have to write a program that takes an array arr[] of size N from the user and sorts the array elements in ascending or descending order using insertion sort.

Q27: Write a program to sort an array using Quick Sort.

In this problem, you have to write a program that takes an array arr[] of size N from the user and sorts the array elements in ascending order using quick sort.

Q28: Write a program to sort an array of strings.

In this problem, you have to write a program that reads an array of strings in which all characters are of the same case entered by the user and sort them alphabetically. 

Q29: Write a program to copy the contents of one file to another file.

In this problem, you have to write a program that takes user input to enter the filenames for reading and writing. Read the contents of one file and copy the content to another file. If the file specified for reading does not exist or cannot be opened, display an error message “Cannot open file: file_name” and terminate the program else print “Content copied to file_name”

Q30: Write a program to store information on students using structure.

In this problem, you have to write a program that stores information about students using structure. The program should create various structures, each representing a student’s record. Initialize the records with sample data having data members’ Names, Roll Numbers, Ages, and Total Marks. Print the information for each student.

We hope after completing these C exercises you have gained a better understanding of C concepts. Learning C language is made easier with this exercise sheet as it helps you practice all major C concepts. Solving these C exercise questions will take you a step closer to becoming a C programmer.

Frequently Asked Questions (FAQs)

Q1. what are some common mistakes to avoid while doing c programming exercises.

Some of the most common mistakes made by beginners doing C programming exercises can include missing semicolons, bad logic loops, uninitialized pointers, and forgotten memory frees etc.

Q2. What are the best practices for beginners starting with C programming exercises?

Best practices for beginners starting with C programming exercises: Start with easy codes Practice consistently Be creative Think before you code Learn from mistakes Repeat!

Q3. How do I debug common errors in C programming exercises?

You can use the following methods to debug a code in C programming exercises Read the error message carefully Read code line by line Try isolating the error code Look for Missing elements, loops, pointers, etc Check error online

Please Login to comment...

  • WhatsApp To Launch New App Lock Feature
  • Top Design Resources for Icons
  • Node.js 21 is here: What’s new
  • Zoom: World’s Most Innovative Companies of 2024
  • 30 OOPs Interview Questions and Answers (2024)

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

Tutorials Class - Logo

  • C All Exercises & Assignments

Write a C program to check whether a number is even or odd

Description:

Write a C program to check whether a number is even or odd.

Note: Even number is divided by 2 and give the remainder 0 but odd number is not divisible by 2 for eg. 4 is divisible by 2 and 9 is not divisible by 2.

Conditions:

  • Create a variable with name of number.
  • Take value from user for number variable.

Enter the Number=9 Number is Odd.

Write a C program to swap value of two variables using the third variable.

You need to create a C program to swap values of two variables using the third variable.

You can use a temp variable as a blank variable to swap the value of x and y.

  • Take three variables for eg. x, y and temp.
  • Swap the value of x and y variable.

Write a C program to check whether a user is eligible to vote or not.

You need to create a C program to check whether a user is eligible to vote or not.

  • Minimum age required for voting is 18.
  • You can use decision making statement.

Enter your age=28 User is eligible to vote

Write a C program to check whether an alphabet is Vowel or Consonant

You need to create a C program to check whether an alphabet is Vowel or Consonant.

  • Create a character type variable with name of alphabet and take the value from the user.
  • You can use conditional statements.

Enter an alphabet: O O is a vowel.

Write a C program to find the maximum number between three numbers

You need to write a C program to find the maximum number between three numbers.

  • Create three variables in c with name of number1, number2 and number3
  • Find out the maximum number using the nested if-else statement

Enter three numbers: 10 20 30 Number3 is max with value of 30

Write a C program to check whether number is positive, negative or zero

You need to write a C program to check whether number is positive, negative or zero

  • Create variable with name of number and the value will taken by user or console
  • Create this c program code using else if ladder statement

Enter a number : 10 10 is positive

Write a C program to calculate Electricity bill.

You need to write a C program to calculate electricity bill using if-else statements.

  • For first 50 units – Rs. 3.50/unit
  • For next 100 units – Rs. 4.00/unit
  • For next 100 units – Rs. 5.20/unit
  • For units above 250 – Rs. 6.50/unit
  • You can use conditional statements.

Enter the units consumed=278.90 Electricity Bill=1282.84 Rupees

Write a C program to print 1 to 10 numbers using the while loop

You need to create a C program to print 1 to 10 numbers using the while loop

  • Create a variable for the loop iteration
  • Use increment operator in while loop

1 2 3 4 5 6 7 8 9 10

  • C Exercises Categories
  • C Top Exercises
  • C Decision Making

Search this site

Powered by Google

Lab 1: Introduction to C

Explore the basics of the C programming language

In this week’s lab, you will:

  • Learn about the C programming toolchain we use in the rest of the course
  • Learn to write simple C programs
  • Practice using getchar , putchar and printf for terminal I/O, as well as command line program arguments
  • Learn about C pointers, and how to implement pass by reference using them
  • Learn to manipulate and handle strings/character arrays
  • Learn about function pointers in C

Preparation #

Log in with your uni ID/password and open the lab template on GitLab here.

Fork the repository by clicking on the fork button. This will create your own copy of the lab repository.

Check the URL bar and make sure it contains your uid (eg. gitlab.cecs.anu.edu.au/*your-uid-here*/comp2310-2023-lab-pack-1 ). This means you are looking at your copy of the lab, and not the template. Once you’ve done this, click Clone and copy the address next to “Clone with HTTPS”.

Type in your uid and password when requested.

  • You should now have a local copy of the repo on your computer. Try moving to the repo’s folder with the cd and ls commands from earlier.

Congratulations! If you’ve got to this point unscathed, you’ve successfully forked and cloned. You’ll revisit Git later when we cover the add, commit, and push part of using Git - after you’ve made some changes to your lab.

Take your time to familiarize yourself with Git early in the course - you won’t be able to submit the assessment items any other way. We also have videos about using Git on a youtube channel.

Recommended only for lab machines: If you don’t want to type in your uid and password every time you pull and push, you can set up Secure Shell (SSH) keys that authenticate without the need for usernames and passwords. See this page for details. However, use of SSH keys requires either being on campus or connecting through the GlobalProtect VPN .

Before we begin, the first step is to set up the software we use in this course.

  • If you are using a lab machine, then congratulations - most of the work has been done already!
  • If you are working on your own computer, there are a few different things to install. You can continue reading the lab while the software installs itself. The instructions are on a separate page here.

Introduction #

The C programming language is designed to target a variety of computing hardware and architectures. In almost all desktop computers and laptops, the architecture is x86 1 . We will be teaching the x86 ISA in the first few lectures in this course. As you will be compiling C code to run on either lab machines or your device, the resulting machine code will be for an x86 processor.

Most of the exercises in this lab do not involve writing any substantial code but rather figuring out how things work . We advise that you note down insights and observations as you do the exercises below, as the resulting knowledge will be helpful in later assessments.

Exercise 1: The Toolchain #

Since you are writing high-level C programs that run on a real CPU, we require a standard system for compiling code: a toolchain . The name is derived from the multiple tools used to get from your program to machine code. For our simple projects, the toolchain consists of three main components:

  • The compiler is responsible for converting each C file into a machine code file known as an object .
  • The linker is responsible for collecting together all of the compiled objects and linking them together into a single executable program.
  • The build system is responsible for ‘gluing’ together the individual components to provide a simple interface for compiling your program.

Let us see the toolchain in action. To use the toolchain, first open the C file src/hello_world.c .

Have a quick read of the file contents. What do you think this program will do?

Open up a new terminal in VS Code (Menu Bar -> Terminal -> New Terminal) and run the command,

Then reopen VS Code with the lab1 folder specifically.

The above make command will trigger the compiler, building the program. If it worked then you should see a new file hello_world in the root lab1 folder. Next run,

This will remove the output of the previous compilation.

Open the file Makefile in your lab folder and have a look through. We do not expect you to be able to write a makefile! However, you should be able to identify a few components. Can you find the commands from earlier? A few other lines of interest include:

  • CC specifies the C compiler to use. We’re using the GNU Compiler Collection (GCC), which will automatically use the linker ld as needed.
  • -march ( m achine arch itecture) explicitly tells the compiler what CPU architecture to target.
  • -Wall ( w arnings all ) enables many of the compiler’s warning messages (though not all of them, despite the name).
  • -Wconversion enables a type conversion warning (see the Types section).
  • -g tells the compiler to generate debug symbols . This is extra information bundled into the executable file that is used by a debugger to correlate the machine code to your program files. Quite handy when you are trying to find out where your code is wrong!

once again to compile your program. The command creates an executable file called hello_world .

This time, run your compiled code (a.k.a. the executable) in the terminal with,

The ./ tells the terminal that you want to run an executable file in the current working directory (i.e., the lab1 folder).

If everything has worked correctly, the following message should appear in your terminal,

To speed up the process, you can combine the commands in to one:

Exercise 2: C Introduction #

The C programming language was developed at Bell Labs by Dennis Ritchie in 1970s. It entered the scene when AT&T was developing the Unix operating system. It quickly rose to prominence as one of the most popular programming languages, a crown that it still holds today. C is notably the first choice for writing low-level software (called systems software), e.g., the Linux operating system.

Systems software interacts closely with the underlying hardware, e.g., for controlling the CPU and memory-resources. High-level user applications (e.g., Spotify) are typically written today in Python and Java. The focus of (user) applications is to provide specific functionality to the user and not to control and manage hardware resources.

Nevertheless, there remains a high demand for C, particularly in embedded systems and IoT and other highly resource constrained environments.

A defining feature of C is that it is closely tied to the underlying hardware. C statements map efficiently to machine instructions. Furthermore, the language is small and simple, leading to efficient code that runs fast and has a small footprint (e.g., the number of instructions).

Therefore, C programs are lightweight compared to other programming languages, such as Java and Python, but are still written at a higher level of abstraction than assembly programs. We will learn in coming labs that C programmers still have low-level access to hardware resources and memory in particular. This last aspect of C is both a blessing and a bane. (You will find out!)

In this half of the course, we intend to teach you how programs written in C interact with CPU and memory at the microarchitectural level. We do not intend to teach programming practices in general and every tiny detail about C’s syntax. We advise you to use the recommended textbook and numerous external online resources for help (when needed).

Every programming language has certain elements critical to writing programs of decent complexity. This tutorial will go through the ones most relevant to the future labs and the second assessment.

Most students in this course have previous programming experience with Java, which has a lot of basic syntax which is similar but is in actuality quite different to C. Think about how C is different from Java as you read the text below.

Variables and basic types #

Declaring variables in C is almost identical to Java, where you have a “type” and then a variable name, and optionally an initial value. Then you can use and modify those variables later in the program as usual.

Also notice how lines of code have to be seperated by semicolons, again same as Java. C is also not whitespace sensitive so any kind of indenting works as long as the semicolons are there to seperate lines, but good code style demands that you indent with caution!

Note that in Java, the compiler would stop you from using a variable without initialising it like below, but although it may raise a warning, C actually doesn’t stop you from doing this ! However, it is undefined behavior and the variable can contain any random value before you use it. So best not.

C’s primitive types mostly mirror Java’s primitive types, and include:

You might notice the lack of booleans and strings. C deals with the same sort of objects that most computers do, namely characters, numbers, and memory addresses. Booleans in particular are represented by int s where 0 means false and any non-zero number means true, which will be elaborated on later. The string type will also be discussed later, though you’ve already seen the use of a string literal; the "Hello, world!" text in src/hello_world.c defines a C string.

If you are interested in what ‘single-precision’ and ‘double-precision’ refer to, then look at the IEEE 754 floating-point number specification .

All of the integer types above can have unsigned prepended to them to make them unsigned integers. For example

The floating-point types are always signed.

The C compiler uses types to know how much space in memory to use for a value and how operations on that value work. For example, we can add two int s fine

But we get possibly unexpected results when adding integer and floating-point types

In the above example, we add x and f in two different contexts: one assigns the result to an int variable, and the other assigns the result to a double variable. The resulting values from the two operations are different.

In this case, the result is being implicitly converted to the result variable’s type (an “implicit cast”). The C standard defines the rules these conversions follow, so correct programs are allowed to do this. However, it is easy to do this accidentally and not realise it, with the mistake causing errors later on in your program. We have added the -Wconversion flag to our compiler flags to make the compiler show a warning when an expression like the one above may result in lost information. In this case, the compiler would show something like

As a general rule, it is safe to add values and store the result to the ‘larger’ type. For example,

Alternatively, if you do an explicit cast using the cast operator like below, then the compiler knows the cast is intentional and does not warn you. You must know what you’re doing though!

Read and predict what compiler warnings related to type conversion will be raised by src/types.c . Also, add any other type combinations that you find interesting.

Next, compile the program and compare the outcome against your predictions.

There are more C types, which we will discuss later.

There is also a special operator in C which we would like you to know. The sizeof() operator gives the size of a variable in bytes. It is used as follows:

What will s be in the above code fragment?

Functions #

We can also define functions with syntax identical to Java, including a function signature with the return type, function name and function arguments with types, and then the function body in curly braces:

A function that returns nothing has return type void .

All executable C programs have a main function which is the entry point of the program. C files need not contain one if they just define library functions which will be used in other programs (see upcoming discussion of Header files), but generally they do. One possible signature for main is:

The “int” that is returned is the exit code of the program. “0”, the standard code, means the program exited normally. Other exit codes may indicate that an error has occurred, and typically the specific number is used to describe specifically what caused the termination so the programmer can debug problems. Some more status codes are described here .

We will see an alternative signature for the main function and talk about its interface in detail later.

Calling a function is done in the same way as Java as well:

The C library provides several useful functions for programmers. The printf function prints its arguments on the standard output, which by default is the screen. Unlike most functions you will come across in C, the printf function takes a variable number of arguments. The first argument is always the message to print (such as "Hello, world!" ). The remaining arguments are data to be substituted into the message. These arguments can be of any type with a corresponding format specifier . The name printf itself is a combination of print and f ormatted.

A format specifier begins with % . For example, %i is the ‘integer format specifier’. If you use this format specifier, then the printf call must have an integer value passed as the next argument. Format specifiers are matched with printf arguments in order from left to right:

  • the first format specifier is expanded to the value of the second printf argument,
  • the second format specifier is expanded to the value of the third printf argument,

A message does not require format specifiers. You can see this in the src/hello_world.c source file, where only the message to be printed is passed. You will see examples of using format specifiers in the following exercises.

Common format specifiers are as follows

You will also notice that the message often ends with \n . The compiler turns \n into the newline character. If we forget to put a \n at the end, then whatever is printed next will be put on the same line. So if you see things like

you might be forgetting to add newlines

src/format_strings.c shows some examples of using printf to print different types of variables. The last two lines with strings and pointers have not been introduced to you yet, but you can see them here in any case.

Build and run the program:

Does it print what you expect? Are there any differences?

Other format specifiers and even modifiers can be applied to the given format specifiers. This reference page shows you all the possible values, with examples. The %li format specifier in the table above is an example of a modifier: the l modifies the i format to accept long (64-bit) values instead of 32-bit.

Some programs become very large and unmanageable in a single source file. Furthermore, some programs are written to be reused across multiple projects. Copy/pasting code across projects makes it hard to keep code up to date, and thus reusing code inevitably leads to multiple source code files. We need a way to ‘link’ these independent program files together during compilation.

The canonical way to support multiple files in C is with header files . These files are given the .h extension, and they define the signatures of all functions exported from the corresponding .c source file.

You have already seen a .h file be used in the previous examples: stdio.h is a header file in the C standard library that defines the printf function signature ( stdio is short for st an d ard i nput / o utput). We write #include <stdio.h> to import these signatures into our program, allowing us to use them. Then, when we run the compiler, the linker includes the associated implementation of printf into our compiled program.

#include is called a preprocessor directive, which behaves as if you replaced it with the contents of the specified file at the exact same location. We will often include the <stdio.h> and <stdlib.h> headers to access the functions declared in those headers, which we can then call from our programs. The <stdio.h> contains the declarations for C standard I/O functions. I/O stands for Input/Output. The <stdlib.h> header contains utility functions we will use in future labs.

Anatomy of a C Program #

Return now to your src/hello_world.c file. You should now understand the whole file as written. This basic format will remain the same across your C projects. Line by line, the file contains:

Includes the stdio.h header that allows use of functions within the stdio (part of the C library). This is required for the printf function.

Is the declaration of the main function , the starting point of your program. int is the return type of the function.

This line calls the printf function, printing the argument to the standard output or stdout, which you see in the terminal.

This statement ends the function. Returning 0 is the usual way to signal that the program has terminated without error.

C uses {} braces for code bodies, () for function arguments, and ; for line terminations. Here we are closing the code body of the main function.

At this point, you can start writing complete C programs. We encourage you to write a small program or two in src/exercise2.c to practice using printf . Try to print the size in bytes of different C variable types using the sizeof() operator.

Exercise 3: Control flow and simple I/O #

Bitwise and logical operators #.

You will perhaps remember the bit manipulation you had to do in COMP2300 using assembly instructions like and , orr , lsl and lsr . C similarly allows you to consider numbers in binary, and provides the following operators for bitwise manipulation. These can only be applied to integral operands.

You can also freely write numbers in hexadecimal (recall 0x means the following number is hexadecimal), and print numbers in hexadecimal using the %x format in printf .

Not all C compilers support 0b to write binary numbers (though yours might ). printf also doesn’t support printing in binary format. However, by now you should know how to convert hexadecimal to binary in your head; ask a tutor if not.

There are also “logical operations” that are used when treating numbers as booleans.

Traditionally, when using integers as booleans, people just use the numbers 0 (to mean false) and 1 (to mean true). However, there are many other numbers that integers can be (e.g. 2, or 40000, or -1). The C spec deals with this by treating ALL nonzero numbers as true. So something like 2 && 3 would be interpreted as TRUE && TRUE , and so output 1 which means TRUE .

Note that bitwise operations and logical operations are strictly different!

The constant Booleans FALSE and TRUE are implemented as 0 and 1 respectively.

Open the file src/bitwise-and-shifts.c and predict the outcomes of the statements in the program.

Open the file src/bitwise-puzzles.c and write missing code for the functions in the file. Each function involves solving a puzzle with the provided constraints. The comments provide the detail for solving each puzzle including the constraints, such as, which operators to use for each puzzle. Write a main() function to test your code.

Relational Operators #

The relational operators include

Each of these operators return 1 if the specified relationship is true, and 0 if it is false. The result type is int (but is intended to be interpreted as a Boolean).

Control Flow #

C has the same standard imperative control flow constructs you’re familiar with from Java, in basically the same syntax. We’ll go over these quickly.

If/else statement example:

While loop example:

Recalling how integers represent Booleans, while (1) {} is how you would write an infinite loop.

For loop example:

Switch statement example (note this is equivalent to the if/else ladder above):

Remember that if a switch case clause doesn’t have a break statement, execution will keep going to also execute the next case’s case! In general you likely want all of your case clauses to have a break statement, but the above code also shows how you can omit the break statement to combine cases.

continue , break and return statements in loops and functions also exist and work as you’d expect as well.

Character type and Input/Output #

C uses char type to store characters and letters. Interestingly, under the hood, the char type is an integer type. C stores integer numbers instead of characters. More specifically, in C, a char variable is stored as one byte in memory with value ranging from \(-128\) to \(127\). To represent characters, the computer must map each integer to a corresponding character using a numerical code. ASCII is the most common numerical code and it stands for American Standard Code for Information Interchange. Take a look at the ASCII table .

The original ASCII defined characters with values from 0 to 127. Later, many countries used the remaining 128 values in a byte to support their local character set, or more symbols. This lead to the possiblility that an email sent from one country could appear corrupted when read in another. The contents were identical, but the computers were picking different (most likely nonsensical) characters to display!

The following code checks to see if a char value is a valid letter from the English alphabet. Note that the condition checks for both lower-case and upper-case letters.

You might be tempted to write 'a' <= c <= 'z' like Python allows, but in C this would not evaluate as you might expect. It first evaluates 'a' <= c to 0 or 1 , and then compares if this is less than or equal to 'z' , which is always true! This is why we split the comparisons and join the results with && .

Checking if c is between the first and last letters works in ASCII because the numerical values of letters are consecutive from a to z and from A to Z (but not from z to A ! There is a gap with symbols in between).

The header file ctype.h provides a number of useful utilities to manipulate characters. You can find several resources with a list and description of useful methods. Here is one useful resource .

getchar and putchar #

It is useful to be able to read one character at a time from keyboard. Each time it is called, getchar reads one character from the keyboard. The code

reads a character from the keyboard and assigns it to the integer variable c . The function putchar prints a character each time it is called:

prints the contents of the integer variable c as a character on the screen.

When interacting with your program through a terminal, getchar does not return as soon as you press a key. The character data is available only when the user presses Enter to indicate they are happy with what is typed. Read more here

Practice Writing Utility Functions #

We will now look at two programs that use control flow and character manipulation. But before that, we want to introduce two useful elements for writing cool programs. The first one is another compiler directive, namely #define . The #define directive allows the definition of constant values to be declared for use throughout your code. Consider the code below

Once we define HIGH and LOW to be 1 and 0, respectively, we can use them anywhere in the code. Any occurrence of HIGH will be replaced with 1 and LOW with 0. More generally, HIGH is a symbolic constant and 1 is the replacement text. Symbolic constants are useful for defining constants at the start of a program and judicious use can improve code readability.

A useful symbolic constant defined in stdio.h is EOF (end-of-file) and it indicates there is no more possible input to read. Detecting the end of input is useful when reading input characters from the keyboard as we will see shortly.

The standard input (keyboard) is treated as a special file in C. A program can read input from the keyboard until the end-of-file is encountered. A user can indicate end-of-file by typing a special key combination. If you are typing at the terminal and you want to provoke an end-of-file, use Ctrl - D (Linux, macOS), or Ctrl - Z (Windows). The following C program reads input characters from screen and prints them on screen until the end-of-file is typed.

Note that EOF is not a character and therefore we must use the larger int type for the variable c against which we compare EOF .

Open the file src/copy1.c and carefully read the code. Compile and run the code. Try to write a condensed version of src/copy1.c in src/copy2.c . Hint: You can do an assignment and test a condition as part of the condition condition in the while loop To test the compiled program, make sure to press Enter each time you input a character from the keyboard.

Open the file src/lines.c and carefully read the code. Can you guess what the code is doing? Compile and run the code to test your guess.

Write a program in src/wordcount.c that counts the number of words typed on the standard input (via keyboard). We define the word as follows: a sequence of characters that does not contain a blank or newline. The program terminates when EOF is encountered.

Interlude: Scopes of Variables #

Let it be noted that variables defined inside functions are only visible inside the function. You can also have variables that sit in the “top level” program outside all of the functions, which are global and visible in all functions.

Control flow statements also define inner scopes in their clauses. For example, a variable defined in an if statement is not visible outside of it:

Instead, we need to define the variable outside the if statement and only initialise it in the if statement:

This is behavior you should be used to from Java.

Exercise 4: Pointers, Arrays and Strings #

Up until now, C has perhaps looked like nothing more than a simple imperative language, but that will all change with pointers and how C directly relies on the user to control memory!

Every type in C has a corresponding pointer type. A ‘pointer’ is C’s terminology for a memory address. Recall that memory is divided into a number of byte or word-sized locations. Each such location has an address. C is unique in that it gives the programmer the ability to manipulate both the address and the contents of any memory location. Therefore, if you have a pointer to a variable, you have the memory address of that variable. Recall in assembly the use of labels. Pointers and labels are similar in the sense that both contain a memory address. C pointers are real variables that exist at run-time (i.e., when the program runs). Learn this statement by heart:

A pointer is a variable that contains the address of a variable.

The syntax for declaring pointer variables is as follows,

Note that we have inserted * between the type ( int in this case) and the variable name ( my_pointer ). Here we have declared that my_pointer is a variable whose value is the memory address of some int variable. The * operator has multiple uses in C. We have just seen a second use above for pointer declaration, and recall the first use was for multiplication.

But we have not initialised this variable with a value. We cannot use it for anything yet, because that would be undefined behaviour, and correct programs do not have undefined behaviour. We can initialise the pointer variable to point to another variable as follows

Here we have put & before the use of x to get the address of x instead of its value.

OK, so now we have a valid pointer pointing to another variable. What can we do with it? Consider the following program.

It is important to note that we have introduced above a third use of the * operator, which is to dereference a pointer. Dereferencing allows us to read or modify the memory at the location pointed to by the pointer (which, remember, is just a variable that holds a memory address).

As we dereferenced p on the left hand side of the assignment, we have used dereferencing to modify the memory pointed to by p . But this memory is of our variable x ! We have changed x without using the name x , except for when we initialised p .

Read the file src/pointers.c . Predict the values of x and p that will be printed before and after the dereference step. Then run

What do you see as the outcome? Does it match your prediction?

The operator & gives the address of another variable. The operator * is the dereferencing operator; when applied to a pointer, it accesses the object the pointer points to.

Return to src/pointers.c and remove the contents of the existing main program. Then, define 3 variables consecutively inside the main program like so:

Now use the reference operator and %p option in printf to print the addresses of these variables. What do you notice? Recall the sizes of int s and long s in C. What does this mean about how these variables are laid out in memory?

Now try adding a global variable that is defined OUTSIDE the main program in the top level scope, and print its address. What do you notice, and why? (Hint: remember from assembly; where in memory might a function store its local variables, and how may that differ from global ones?)

Pointers represent memory addresses, which are really just numbers. This means you can cast any integer to a pointer, and while C might give a conversion warning, it will not error. Return to src/pointers.c and write code to read the contents of the address 0 (or any other random address). What happens?

You will learn more about the error you got in the last exercise when you study the operating system and virtual memory in lectures, but for now just understand that you can’t freely access memory like on the Microbit. The memory has to be allocated to your application, e.g. when you define a global variable the compiler knows to allocate memory for it.

An array is a block of consecutive variables or objects of the same type. The declaration below declares an array named marks of 5 integers.

Array subscripts start at zero in C. The elements of the marks array can be accessed as follows: marks[0] , marks[1] , marks[2] , and so forth. A subscript (enclosed in square brackets) can be any integer constant or expression. In general, the notation marks[i] refers to the i-th element of the array. We refer to i as an array subscript (or index) and the process of accessing a specific element of an array as indexing an array .

The five integers in the marks array are stored next to each other in memory. If the first element of the marks array is stored at an address 0x00000000, then the next element is found at 0x00000004. The 4-byte increment is because each element of the array is an integer and each integer is four bytes in size.

It is possible to initialize an array during declaration. For example,

You can also initialize an array like this.

Here, we haven’t specified the size. However, the compiler knows its size is 5 as we are initializing it with 5 elements. The assignments below change the values of the first (at index 0) and fourth (at index 3) array element from their initial value.

There is a strong relationship in C between arrays and pointers. Any operation that can be achieved with indexing can be done with pointers as well. The name of the array is a synonym for the location of the first element. Therefore, a reference to marks[i] can also be written as *(marks+i) . The following two C statements are equivalent,

In evaluating marks[i] , the compiler converts it to *(marks+i) immediately; the two forms are equivalent.

Recall that a pointer variable contains the address of a memory location. The following C statement declares a pointer named pa and initialises it with the address of the first element of the marks array.

Note that pa now points to the first element of the marks array. We show where the pointer pa points to in the figure below.

The C programming language allows pointer arithmetic. This means that we can add an integer to the pointer pa and make it point to any other element of the marks array.

For example, pa+1 points to the second element of the marks array, i.e., marks[1] (see top of the figure). Note that pa , pa+1 , and pa+2 contain the addresses of the array elements and not their contents. These remarks are true regardless of the type and size of variables in the marks array. The meaning of adding 1 to a pointer , and by extension, all pointer arithmetic, is that pa+1 points to the next object, and pa+i points to the i-th object after pa .

To obtain the contents of an array element, we need to use the dereferencing (*) operator. Dereferencing is depicted at the bottom of the figure. For instance, *(pa+2) refers to the value stored at the memory address pa+2 .

pointers

The following two C statement both print the value of the third element of the marks array,

Consider the following two C statements. Their behavior is equivalent.

The close relationship between arrays and pointer should now be obvious. Consider the first statement above. We have just indexed the pointer pa to access an element of the array (marks[2]) ! The compiler knows that we want to access the third integer in the marks array. Similarly, in the second statement, when we add 2 to pa , the result (memory address) points to the third element of the array.

Behind the scenes, when we say pa[2] , the compiler multiplies the array subscript by 4 because each array element is four bytes in size in this case. Similarly, when we add 1 to a pointer of type int * , the compiler increments the pointer (memory address) by 4 units instead of just 1 because each integer is 4 bytes in size. Make sure the figure above makes perfect sense and talk to your tutor if you feel confused.

Finally, the following two statements are equivalent and both assign the address of the first element of the marks array to the pointer variable pa .

We often say that arrays decay to pointers. This behavior is especially true when passing arrays to functions as arguments, which we will discuss in a moment.

C does zero bounds checking at runtime; it is very easy to access an index of an array that is beyond the end of its length. This has implications for security as it potentially allows users to access memory they’re not supposed to, as well as program correctness! Most of the time, a segmentation fault will notify you of the error, but sometimes it can be incredibly tricky to debug…

Read the src/arrays.c program and predict the outcome. Then run the program and check if your predictions match the actual outcome. The program should print a warning. Make sure you understand what this warning is about.

In src/arrays.c , write a C program where you define an array of integers, and print the address of each element of the array using the subscript ( [] ) and reference operators ( & ). Confirm that the elements really are contiguous in memory.

Recall how we described pointer arithmetic. Consider the following code:

Put this in src/arrays.c , compile and run it. How much does p increment by each time 1 is added? Now try changing all the instances of “int” in the above code to “char”. Does your answer to the last question change? Why should it?

Controlling pointer arithmetic will be important especially in your first assignment.

C Strings #

Unlike Java, which has a dedicated String object type, strings in C are just arrays of char values. So the following is a C string

When you create a string in this way, the C compiler will automatically write a byte with value 0x00 at the end. The resulting array is called a null-terminated string, and is a method of determining the length of the string without tracking the length in a separate variable. So the above string initialises memory the same in the following way,

Single and double quotes mean different things in C. Single quotes are used to create char values. Double quotes are used to create string values (an array of char values ending with a null (zero) byte).

Similar to \n , the sequence \0 is replaced by the C compiler with an actual null byte.

It is not possible to modify a string declared using "..." syntax. Trying to do so is undefined behaviour. It might even work on your computer, but fail on someone else’s.

Open the src/string-util.c file and implement string_cpy and reverse functions. Don’t forget to set the trailing null bytes!

Exercise 5: Pointers as function arguments #

Recall how functions in C look like from the last tutorial. Functions take input arguments and they optionally return a value. C passes arguments to functions by value (affectionately called pass by value or call by value ). These phrases mean that when we provide an argument to a function, the value of that argument is copied into a distinct variable for use within the function. Consider the following function that tries to swap two arguments. We call this version of swap the version 1 or v1 .

We first safely store a in a temporary local variable called temp . We then do the swapping. Run the program src/swap-v1.c and you will find something strange. Although a and b have been successfully swapped inside the swap function, when we print a and b from the main function, we still see their original (non-swapped) values.

Why do you think this is happening?

We can use pointers to rewrite the swap function. We can use the indirection ( * ) operator to declare pointers and dereference them. Consider the following prototype of the swap_v2 function

Passing arguments in the above fashion is called pass by reference . We are not providing the swap_v2 function the actual values of a and b . Instead, we are passing a reference to a and b . In other words, we are providing the swap_v2 function with the memory addresses of a and b .

Open src/swap-v2.c and write the code to swap a and b in the swap_v2 function. Test that your new function definition swaps the input arguments, so even from the main function, when we print a and b , we observe their values swapped. Does your new function work?

Pointers as function arguments serve another important purpose. Recall that C functions can return only a single value. What if we want our functions to return more than one value? We can pass the address of a variable to a function (i.e., pass a pointer as the input argument). The function can then assign a value to the variable pointed-to by the input argument using the dereference operator.

Read the program in src/return2.c . What value of ret1 and ret2 do you expect to see on the screen? Run the program and test your hypothesis.

Open src/sum.c and complete the function definition of the sum function. Make sure it correctly sums up the elements of the input array to the function. Note again that array arguments can be passed to functions as pointers, and we can do the usual pointer arithmetic on these input arguments. So, sum(int *array) is the same as sum(int array[]) .

Write a program that compares two strings character-wise and returns a 1 if the two strings are equal. Otherwise, the function returns 0. Write your code in src/strcmp.c . You can rely on the caller to pass null-terminated strings to the str_cmp function.

Write a function that takes a null-terminated string as an input argument. It computes and returns the length of the string. Test your function by writing a C program with a main function. Write the code in src/strlen.c .

Exercise 6: Program Arguments #

Program arguments are a list of values passed to our program when it is run. You have used this when you ran the make command:

runs the program make with the argument hello_world . That is how make knows to build that particular file.

In C, we can get the program arguments from the argc and argv parameters to main . The function signature for main now looks like

The type of the char **argv parameter looks new, but you have seen this type before: it is just two levels of pointer ! argv is a pointer to a pointer to a char . If you read the memory pointed to by argv , the value you get is itself a pointer of type char * . If you read the memory where that pointer points, then you get a char value.

But these argv related pointers are not just to single values — both levels of pointer are to arrays of values. So really argv is an array of C strings , which themselves are null-terminated arrays of char values.

Also remember that C arrays do not store their length like you might be accustomed to in Python. This is fine for the C strings, because they end when we see a null byte, but how long is the overall argv array? This is where argc comes in: argc is the number of elements in the argv array. The given names are actually abbreviations:

  • argc : arg ument c ount
  • The term vector in the context of programming refers to a resizable array-like structure. The use of ‘vector’ in the name argv is historic; as you cannot change its size, array is a better description of the actual data structure.

So altogether we have an array of string arguments argv , this array having length argc , and each element in the array is a pointer to a null-terminated C string.

In src/exercise6.c , write a program that, assuming there is at least one program argument,

  • On the first line prints There are X arguments where X is replaced with the number of program arguments
  • On the next line, print The first argument is Y where Y is replaced by the argument at index 0
  • On the next line, print The following arguments are
  • Then print the rest of the arguments (from index 1) on a new line each with the format: - Z (size: N) where Z is the argument and N is the sizeof that argument
  • Then, on the next line, print The total size of the arguments is T where T is replaced by the sum of the sizeof the arguments.

Refer back to earlier sections (as needed) for using pointer values and printing formatted strings.

Build and run your program with different arguments. E.g.,

Do you notice something odd?

Exercise 7: Function Pointers #

Function pointers are pointers that point to executable code (typically other functions). They are used to treat functions as regular data. This means that it is possible to define pointers to functions, which can be assigned, placed in arrays, passed to functions, and even returned by functions. Unlike regular pointers, the type of a function pointer is described in terms of a return value and parameters that the function accepts. Declarations for function pointers look as follows:

The above declaration means that we can set match to point to any function that accepts two int pointers and returns an integer. If we have a function match_int as below,

We can set match to point to the above function with the following statement:

To execute a function referenced by the function pointer, we simply use the function pointer where we would normally use the function itself. For example,

Function pointers are useful for encapsulating functions into data structures. Typically, a function pointer is made a member of a data structure, and the pointer is used to invoke one of the many functions based on the type of data that is stored in the data structure. The Advanced Exercises exercise at the end of this handout helps you to explore this use of function pointers further.

Open the file src/reduction.c and read the comments to fill in the missing parts of the code. The compiled code should either reduce the samples array using reduce1 or reduce2 depending on whether the user runs the compiled binary with -r1 or -r2 argument on the command prompt. If you write the code we ask for properly, you will observe the output 55 for -r1 , and 3628800 for -r2 .

Advanced Exercises #

The following exercises are a little more difficult, however you are still expected to complete them. Ask your tutors if you get stuck.

Write the program tail , which prints the last n lines of its input. By default, n is 5, but it can be changed by an optional argument, so that tail -n prints the last n lines. The program should behave gracefully regardless of the input or the value of n . You can store the lines in a two-dimensional array of fixed size. If the user enters more lines than a threshold, or lines bigger than the maximum length, the program must terminate gracefully. Note that we do not provide you a template for this program. You need to write this program from scratch.

If you already know how malloc() works, try to make the best use of available memory instead of using a two-dimensional array of fixed size.

The next exercise explores the power of function pointers with a sorting program that either sorts lines input by the user (via keyboard) lexicographically or numerically. Specifically, if the optional argument -n is given, the program will sort the input lines numerically. A sort typically consists of three parts: (1) a comparison that determines the ordering of a pair of objects (e.g., numbers), (2) an exchange that reverses their order, and (3) a sorting algorithm that makes a sequence of comparisons and exchanges until the objects are in a proper order. Note that the sorting algorithm is independent of the comparison and exchange operations, so by passing different comparison and exchange functions to it, we can arrange to sort by different criteria. Let’s explore this decoupling of concerns via function pointers in the exercise below.

Open the source file src/qsort.c . The source code read lines from the input and sorts them lexicographically using the quick sort algorithm. You do not need to understand quick sort to solve this problem, but if you have the time to explore it, that would be great! Run the program and type a bunch of lines and then enter EOF and observe the output. Now, we would like to add the -n option to the program that sorts the lines entered from the keyboard numerically. Write a function called numcmp that takes two input strings and convert them to double types, and returns -1 if the first argument is less than the second argument, and 1 if the first argument is greater than the second argument, and 0 otherwise. You can use the atof utility function to convert a string to a floating point value. If the user enters -n at the command prompt, then the program should compare the input lines numerically. To test numerical sort, you can input one integer per line from the keyborad and then enter EOF . Note that you will need to change the line in the main function that calls quick_sort .

Note that the src/qsort.c program makes use of generic void* pointers. Normally, C allows assignments only between pointers of the same type. A generic pointer in C is declared as a void pointer in C and it can be assigned to a pointer of any type. Once again, generic pointers are useful for implementing data structures, which we will explore in the future.

Extension: Debugging #

So far you have only ran your programs in the terminal, using printf statements to gain insights in to what is happening. Fortunately, VS Code’s debugger has support for C. To get started, open the ‘Run and Debug’ panel and pick the program you want to debug in the drop-down menu. Press the play button, and VS Code will build and run your code through the debugger.

Resources #

cppreference : Comprehensive C reference pages. This reference is not a tutorial. It is instead a reference for the entire C language. It contains a lot of content and terminology we do not cover. It is most helpful for reviewing documentation and usage of specific functions, such as printf .

codecademy : Free external resource for learning C.

x86 is a CISC ISA that originally debuted on the Intel 8086 in 1978. It has since been expanded from 16 to 32, to 64 bits. Featuring variable instruction lengths, over 30 extensions, many addressing modes, and full backward compatibility (16-bit code will run on a modern 64bit processor!), x86 is rightfully considered one of the most complex ISAs to exist.  ↩

C++ Lab Assignments

You can not learn a programming language by only reading the language construct. It also requires programming - writing your own code and studying those of others. Solve these assignments, then study the solutions presented here.

  • Assignments
  • Office hours
  • Forum & email
  • Other resources

Lab 1: C programming under Unix

Lab sessions Mon Jan 16 to Thu Jan 19

Lab written by Julie Zelenski

During lab, you will experiment and explore, ask and answer questions, and get hands-on practice in a supported environment. The lab exercises revisit topics from recent lectures/readings and prepare you to succeed at the upcoming assignment. To record lab participation, we use an online checkoff form that you fill out as you work. Lab is not a race to answer exactly and only the checkoff sheet-- these questions are deliberately trivial and we use them merely to record attendance and get a read on how far you got. Instead of using the checkoff questions to dictate your activity, let satisfying your curiosity and achieving a sense of mastery be your guide. The combination of active exploration, give and take with your peers, and the guidance of the TA makes lab time truly special. Your sincere participation can really accelerate your learning!

Learning goals

During this lab you will:

  • get further practice with common unix utilities
  • work through editing, compiling, testing, and debugging C programs in the unix environment

Find an open computer to share with a partner and introduce yourselves. Together the two of you will tackle the exercises below. Everyone is encouraged to discuss issues and share insights with all your labmates. The TA will circulate to offer advice and answers and keep everyone progressing smoothly.

Get started

We distribute all labs and assignments as Mercurial repositories. If you haven't yet, read the CS107 mercurial guide and be sure both partners have set up their Mercurial environment. Clone the lab repo by using the command below. This command creates a lab1 directory containing the source files and Makefile.

Pull up the online lab checkoff right now and have it open in a browser so you can jot things down as you go. Only one checkoff needs to submitted for both you and your partner.

Lab exercises

Part 1: unix practice.

This warmup exercise is to try out the unix commands suggested below while simultaneously chatting up your labmates about your assign0 experiences. How is everyone doing so far on getting comfortable in the unix environment? Do you have open questions or an issue you'd like help with? Did you learn a nifty trick or two that you'd like share? Let's hear it!

  • man . The online manual is available via the man command. There are man pages for unix utility programs (try man ls , man rm , ...) as well as C library functions ( man fopen , man strncmp , ...). Man is your go-to to learn the usage for a command, review option flags, or find a function prototype. If you aren't sure of the exact command you need, try man -k <term > to get a list of man pages related to a search term ( man -k calculator , man -k quota ).

printenv and env . Read their man pages for these two commands to see the basic usage. Here is some suggested usage to try out and see how these commands operate.

  • Run printenv with no arguments and skim its output. What is the response if you invoke with an argument such as printenv USER ? What if you make a bad request printenv BOGUS ?
  • Run printenv , then run env BINKY=1 WINKY=2 printenv . What changes between the two? Run printenv again to determine whether those changes were transient or permanent.
  • Run date then env PATH=/tmp date . Can you explain why the date command failed to execute in the second case?

grep . When searching files for lines that match a given pattern, grep is just the tool for the job. In its simplest usage, grep finds occurrences of a fixed string (no metacharacters), but where grep especially shines is in matching complex patterns expressed as regular expressions (commonly shortened to regex). Standard grep is quite feature-rich, so we highlight a few key features to get you started rather than ask you to wade through its (overwhelming) man page.

  • Adding the color flag ( grep --color ) will highlight the matched portion of the line in red. The red highlight makes it possible to see exactly which chars matched. I find this so helpful that I defined a shell alias to make grep expand into grep --color so I never have to be without it, you may want to do the same. (Note: if grep --color doesn't display in red in your terminal, make a shout out on Piazza asking for help getting color configured for your specific terminal program)

Here are the four core metacharacters that you will implement in assignment 1:

Practice forming regular expressions using these metacharacters and testing them using grep. The slink/dictionary file is a list of English words. Grep the dictionary file for practice, e.g. grep joy slink/dictionary should report all words that contain joy . Here are some suggested exercises:

  • match all words that end with zy
  • match all words that start with k and end with k
  • match all words that are exactly 7 letters long

Certain punctuation characters have special meaning to the shell. For example, * and $ in a command-line argument are expanded by the shell before passing them to the program. Enclose a pattern in single-quotes to suppress these special treatment. Run the command grep my* slink/dictionary and compare to grep 'my*' slink/dictionary to see the difference. The shell expands the unquoted my* to all filenames in the current directory that start with my . Run the command echo my* to see the arguments after the shell has expanded the wildcard. Where should you add the quotes to the command grep my* my* to search for the pattern my* in all files starting with the letters my ?

(Aside: Do you dream of having regex superpowers ? I highly recommend visiting regexone.com for its great interactive tutorial and lots of regex practice problems -- check it out!)

make . All CS107 programs are built via a makefile. Although you can compile a single file C program with a direct call to gcc file.c , if your project is larger or requires more complicated settings, a gcc command becomes unwieldy. Creating a makefile automates the build commands into a single request to make . By tracking file timestamps, make can be smart about rebuilding only those components that are out-of-date.

Run make and observe it building the programs, narrating each step in the build process. Run make again and observe that it is clever enough to realize nothing needs to be rebuilt, as no source files have changed. Make a trivial edit to one of the source files and then run make again to see that it will rebuild any component that depends on the changed file. Run make clean to remove all previous build products. This gives a fresh start. Any subsequent make will rebuild everything from scratch.

(Aside: you will not be asked to construct your own makefiles and only occasionally will edit ours, but if you're curious to know more, open our makefile and read along with the comments to get a rough sense of how they are structured or check out the CS107 guide to makefiles .)

Part 2: Implement and test myprintenv

Review, edit, and build the code.

The myprintenv.c file is a partial implementation of a printenv-like program. The given code correctly handles printing the list of all environment variables, but is missing the handling for when invoked with an argument. Pull up the printenv man page to review how it supposed to handle it and then edit myprintenv.c to implement that behavior. Use make to build the program.

Test manually

Once your code successfully builds, it's time to test! One simple means to verify correctness is by comparing your results to a known-correct solution. For example, run printenv PATH then run myprintenv PATH and manually eyeball the outputs to confirm they match. Even better would be to capture those outputs and feed them to diff so the tools can do the work. To make testing as painless as possible for you, we've automated simple output-based comparison into a CS107 tool called sanitycheck . You'll use sanitycheck throughout the quarter, so let's get some practice using it right away!

Test with sanitycheck

First read our sanitycheck instructions , then try running sanitycheck in your lab1 directory. Follow along by reading the report sanitycheck produces while running. A test case will run a myprintenv command, compare its output to the solution, and report if mismatched. The default sanitycheck for lab1 runs four test cases on myprintenv. How did your implementation fare on them? If you passed all four, great for you! It not, review the sanitycheck report to learn which one(s) you didn't pass. Follow up with some manual testing until you fully understand the problem, edit your code to resolve it, and build and test again. Repeat until your version passes all the sanity tests for myprintenv.

Part 3: Test and debug mywc

The mywc.c file implements a wc-like program ( man wc ) that is intended to count the lines, words, and characters from a file and report the longest line. The code was written by your colleague who claimed it is "complete", but on his way out the door he mutters something unintelligible about possible unfixed bugs. Uh oh... Your task is to test and debug the program into a fully functional state using CS107 sanitycheck and the gdb debugger. Take a moment to skim the CS107 guide to gdb before going further.

As always, your first task when given a piece of code is to carefully read it over. Do you understand its intended purpose and how it operates? Does the code use any techniques or library functions that are unfamiliar to you? Here are a few issues to discuss with your partner:

  • How does the program handle reading either stdin or a named file?
  • How does the code strip the trailing newline from the fgets result? When might the fgets result not end in a newline? What does the code do in that case?
  • How can you find out whether isspace is merely a test for ch == ' ' or does fancier?
  • What is the purpose of the %6d in the printf format?

Run mywc on a few different files and/or inputs typed to stdin. It seems to be printing fairly reasonable numbers for the count of characters and lines, but that word count is clearly bogus. Not only is it hopelessly wrong, it seems to vary from run to run on the same exact file. That's definitely no good! Let's use the debugger to better understand what's going on. Run gdb mywc to start the debugger and set a breakpoint on the line number within count_words where count is incremented. Run the program under gdb and when stopped at the breakpoint, print count before the increment. This should quickly draw your attention to the fact that count is garbage from the get-go-- d'oh, the variable was not initialized! In a safety-conscious language such as Java, code that uses a variable uninitialized is rejected during compilation. Do a make clean and make to review the build warnings and you'll see nary a peep from the C compiler about it. Up your vigilance, now knowing the C compiler can be pretty lax!

Initializing count is an easy fix. Edit, build, and re-run. You should observe that word count now has a reasonable value and stays consistent from run to run. You could try to hand-verify if word count is truly correct, but an easier approach is to run sanitycheck now. Uh oh -- it appears there is still work to be done. Get the program back into gdb, run on the input file for the first test case and step through the execution. Once you understand the miscounting issue, fix, build and re-run.

Your program should now pass the first and second mywc sanity test cases. Hitting a milestone is a good to make a commit, so do that now.

Next up is figuring out what's wrong with the third sanity test case. It matches on character/word/line counts, but reports the wrong longest line. But wasn't longest line already working correctly on the first two cases? If you do some further testing and make careful observations, you'll figure out that the true behavior of the program is that it always prints the last line. It's up to you to find and fix the underlying root cause. Consider: Is it failing to properly determine which word is longest, not properly storing the longest so far, printing the wrong thing at the end, or something else entirely? Get back into gdb, edit to fix and test.

The program should now pass all three default sanity cases -- way to go (and time to make another commit)! Does achieving sanity success mean your work is done? These three test cases are a good start on a testing plan, but aren't fully comprehensive coverage by themselves. Are there other test cases that could shake out problems as yet undiscovered? Quite probably. In fact, depending on the fixes you've made thus far, I strongly suspect you still have a bug remaining in count_word. None of the default sanity test cases expose this particular bug, so you'll need to work up a new test case to flush it out. Creating your own custom tests for sanity is explained in the sanitycheck instructions . The custom feature allows you to broaden your testing coverage and thereby increase your confidence that your program can handle anything we might throw at it in grading. Create a custom sanity test that runs mywc Makefile and you'll likely be rewarded with evidence of a heretofore unknown bug. Exposing bugs is the name of the game, as you can't fix what you don't know about. Long live sanitycheck!

Part 4: Give valgrind a whirl

Although your programs aren't yet making heavy use of memory/pointers, they will soon and a skill you want to add to your arsenal is using Valgrind for its help detecting memory errors. If you haven't already, review our guide to valgrind and let's try out Valgrind now.

We will need a buggy program for this so temporarily edit mywc.c to remove the earlier fix you made to initialize count. Build the program and test to see you have the previous bad behavior with the wacky word count.

Now run that buggy program under valgrind. How does valgrind report the problem? At the end of the valgrind report will be a recommendation to re-run with additional flags to provide more information. Re-run valgrind, adding its suggested flags. Does this information help you connect the errors reported to the root cause? Sweet!

Becoming a skilled user of Valgrind is invaluable to a programmer. We recommend that you run Valgrind early and often during your development cycle. It's best to focus on one memory problem at a time. Your strategy should go something like this:

  • run all newly-introduced code under Valgrind
  • stop at the first error reported
  • study the Valgrind report and follow the details to suspicious part of the code
  • ferret out root cause
  • resolve the problem
  • build and re-test to see that this error has gone away

Repeat for any remaining errors. Don't move on until you get a clean report from Valgrind. Note that memory leaks don't demand the immediate attention that errors do. Leaks can (and should) be safely ignored until the final phase of polishing a working program.

Check off with TA

At the end of the lab period, submit the checkoff form and ask your lab TA to approve your submission so you are properly credited for your work. It's okay if you don't completely finish all of the exercises during lab; your sincere participation for the full lab period is sufficient for credit. However, we highly encourage you to work through any unattempted parts or unresolved issues to solidify your knowledge of this material before moving on! Try our self-check to reflect on what you got what from this lab and whether you're feeling ready for what comes next!

« Back to the main CS 300 website

Lab 1: C Programming and Makefiles

  • Due Tuesday, February 6th at 8:00 PM EST

:warning:

1. Completed Lab 0 – This will ensure that your Docker container and grading server account are set up properly.

2. Completed the Diversity Survey – Your grades for Lab 0 and Lab 1 will depend on whether you’ve submitted this (though you don’t have to answer any of the questions).

3. Signed up for Section on CAB – Our first round of sections will start this week!

Introduction

The purpose of this lab is to give you some experience with writing and understanding the syntax of C programs and apply the tools used to compile and run them. After this lab, you will also be more familiar with pointers and why C and C++ use them.

If you take away anything from this course, hopefully, it’s that Computer Systems are not magic and that much of it actually makes a lot of sense. Don’t be afraid to look up questions on Stack Overflow and Linux Man Pages (which provide great documentation on C library functions), and if that doesn’t help, ask on EdStem!

Here are some of highlights: C is an imperative programming language that was mainly developed as a systems programming language to write operating systems. The main features of the C language include low-level access to memory, a simple set of keywords, and clean style, these features make C suitable and widely-used for system programming. C gives you a huge amount of power over what the computer does, which helps optimize the performance of your programs and allows writing low-level sofware that interacts directly with hardware. It also gives you the awesome feeling of really being in control . But with that power comes the responsibility to use it correctly: C has very few safeguards to protect your program’s data or exit gracefully when you make mistakes, and it will happily overwrite your memory with garbage or make your program explode if you make mistakes. Don’t worry, though, we’ll help you find and avoid them!

If you are looking for a detailed tutorial on C, check out the links on our C primer .

  • Assignment Installation

Start with the cs300-s24-labs-YOURNAME repository you used for Lab 0.

First, ensure that your repository has a handout remote. Type:

If this reports an error, run:

This will merge our Lab 1 stencil code with your previous work.

You may get a warning about “divergent branches” like shown below:

lab assignment c programming

This means you need to set your Git configuration to merge our code by default. To do so, run (within the same directory in your labs repository workspace):

If you have any “conflicts” from Lab 0 (although this is unlikely), resolve them before continuing further. Run git push to save your work back to your personal repository.

Assignment Part I: C Programming

In this part of the lab, you will be writing a program that will reverse an array of strings (or, as they are known in C, char pointers). You will be writing two functions in the file reverse.c and you will test your implementation with the code found in test_reverse.c .

After you set up the lab, you should find within the lab1 folder a couple of files:

Header Files You’ll notice that there are three files in the provided stencil code. reverse.c and test_reverse.c are similar to what we’ve seen before, containing C code. But what about reverse.h ?

files ending in the .h extension are called header files , and declare functions so that they can be used in multiple different .c files. Without a header file, reverse_test.c wouldn’t be able to use the functions that you create in reverse.c , which would make testing impossible!

reverse.h includes the signature of the reverse_arr function, but with no implementation:

and then reverse_test.c has this line at the top, telling the C compiler to look for function definitions in reverse.h

This way, when the reverse_arr function is used in reverse_test.c , the C compiler checks reverse.h for a matching signature, and then checks reverse.c for an implementation of the function.

  • Review of pointers and strings

Pointers are memory locations that store addresses (i.e., they “point” at whatever is at that address!). For instance, int* is a pointer to an integer. On a 64-bit architecture (which most computers today use), the pointer occupies 8 bytes in memory, which store the address it points. And that address refers to the first byte of a 4-byte sequence of memory that stores an int .

As you will notice, there isn’t an explicit data type called “string” in C. That’s because strings in memory are just a sequence of one bytes, each represented as a char (a 1-byte value). Instead of having a datatype explicitly called “string”, in C, you can think of char pointers (i.e., char* ) as strings.

Since store is defined as a char pointer, store will point to a byte of memory that stores a character. And if you increment the value of the pointer by 1 (going to the next box) and dereference that value, you would get the next character of the string. This raises the question: couldn’t you just keep incrementing this pointer? How would you know where the end of the string is?

The answer: all strings in C are terminated by a NUL byte (a char storing numeric value 0), also known as \0 . This byte indicates that you have reached the end of the string.

Consider the following memory layout for the example program above:

Now think through what happens at each iteration of the loop:

  • if i == 0 , then *(store + 0) dereferences the memory address stored in store , which is 0x2000 , and at address 0x2000 , the character “h” is stored as a single byte. This is equivalent to writing store[0] .
  • if i == 1 , then *(store + 1) dereferences the memory address stored in store + 1 , which is 0x2001 . At 0x2001 , there is the character “e”. This is equivalent to writing store[1] .

What you saw here is an example of pointer arithmetic , that is, arithmetic on memory addresses.

  • Let’s get coding!

Task: You will be writing two functions in the file reverse.c :

  • Note : you can assume that you will have the same number of elements in the array as specified by the second argument, and you will not have to reverse an empty array and all elements will be defined (i.e., not NULL ).
  • swap will take in two elements from the array and swap them. Note : Remember that pointers are also passed by value as an argument to a function, meaning a copy of the address is passed. Thus, if you make changes to the value of the pointer, the address to which the pointer points changes (rather than the memory object that the pointer points to). Since the address value is copied when passing a pointer, changes to the address will not be reflected outside that function.
  • Running and Testing

Once you have finished writing your code, you are ready to test your implementation!

To test your code, we provide a file called test_reverse.c which calls reverse_arr , which you implemented in reverse.c . In order to run it, you must compile it and link the two files together into an executable by running the following command:

This generates an executable called reverse_test . An executable is a special file that contains machine instructions which are made up of machine instructions encoded as 0s and 1s. And running this file causes the computer to perform those instructions.

In this case, those instructions are to run the program starting from main() , which first parses input from the command line, reverses the array given, and calls functions that run the tests found in test_reverse() . (One of the tests will open a file called test.txt in the current directory, reverses each line of the file, and writes it to an output file called testout.txt. )

You run your executable via:

For instance:

will print out the results of reversing the input array and running the test suite. If you fail a test, the output provides the expected result at a given index in the array and the actual result.

To debug, you may find it helpful to print what’s happening in your swap and reverse_arr function. For instance, if you wanted to print the variable store from the code sample above, you can do:

to print a string.

Hint : If you want to print out the value of a pointer, use the %p syntax for printf.

Once all of your tests pass, you are ready to move on!

Assignment Part II: More on Compiling

As you saw from the previous section, you compiled your program by running:

With the -o flag, you can direct the output of the gcc compiler into a file specified by the argument following the flag. If you didn’t use the -o flag, you could run:

And this will produce an executable file called a.out (this is just a default filename defined by the compiler), which you can run by typing ./a.out .

The gcc compiler supports the use of hundreds of different flags, which we can use to customize the compilation process. Flags, typically prefixed by a dash or two ( -<flag> or --<flag> ), can help us in many ways from warning us about programming errors to optimizing our code so that it runs faster.

The general structure for compiling a program with flags is:

Warning Flags:

  • Uninitialized and unused variables
  • Incorrect return types
  • Invalid type comparisons
  • The -Werror flag forces the compiler to treat all compiler warnings as errors, meaning that your code won’t be compiled until you fix the errors. This may seem annoying at first, but in the long run, it can save you lots of time by forcing you to take a look at potentially problematic code.
  • Assert statements that always evaluate to true because of the datatype of the argument
  • Unused function parameters (only when used in conjunction with -Wall )
  • Empty if/else statements.

Task : Add the -Wall , -Werror , and -Wextra flag when compiling test_reverse.c and fix the errors that come up. Notice that in test_reverse.c the main() function takes in two parameters:

  • argc indicates the number of arguments passed into the program.
  • argc == 1 then only the test suite should be executed
  • The number of elements to be reversed
  • The elements to be reversed
  • For example: ./reverse_test 2 csci 300
  • For example: ./reverse_test 2 csci should cause an error
  • Make sure to return 1 from main on an error, so that the OS can detect that your program exited with errors.

Debugging with Sanitizers: The warning flags don’t catch all errors. For example, memory leaks, stack or heap corruption, and cases of undefined behavior are often not detected by the compiler. You can use sanitizers to help with identifying these bugs! Sanitizers sacrifice efficiency to add additional checks and perform analysis on your code. You will be using these flags in the next lab in greater detail.

-fsanitize=address

  • This flag enables the AddressSanitizer program, which is a memory error detector developed by Google. This can detect bugs such as out-of-bounds access to heap / stack, global variables, and dangling pointers (using a pointer after the object being pointed to is freed). In practice, this flag also adds another sanitizer, the LeakSanitizer, which detects memory leaks (also available via -fsanitize=leak ).

-fsanitize=undefined

  • This flag enables the UndefinedBehaviorSanitizer program. It can detect and catch various kinds of undefined behavior during program execution, such as using null pointers, or signed integer overflow.
  • This flag requests the compiler to generate and embed debugging information in the executable, especially the source code. This provides more specific debugging information when you’re running your executable with gdb or address sanitizers. You will see this flag being utilized in the next lab.

Optimizations In addition to flags that let you know about problems in your code, there are also optimization flags that will speed up the runtime of your code at the cost of longer compilation times. Higher optimization levels will optimize by running analyses on your program to determine if the compiler can make certain changes that improve its speed. The higher the optimization level, the longer the compiler will take to compile the program, because it performs more sophisticated analyses on your program. These are the capital O flags, which include -O0 , -O1 , -O2 , -O3 , and -Os .

  • This will compile your code without optimizations — it’s equivalent to not specifying the -O option at all. Because higher optimization levels will often remove and modify portions of your original code, it’s best to use this flag when you’re debugging with gdb or address sanitizers.
  • This will enable the most aggressive optimizations, making your code run the fastest.

Task : Time your program before you add the -O3 flag and then after you’ve added the -O3 flag to your compilation. Because this program is so small, you probably won’t be able to detect a difference in speed, but in future assignments where there is a lot more code, the optimization flag will come in handy. The -O3 flag will ask the compiler to examine what your code is trying to do and rather than following the provided code verbatim it will replace it with machine instructions that functionally do the same thing, but in a more efficient manner. You can time your program by running the time command in your Docker container. For this exercise, pay attention to the real time, but if you’re curious about the different types of times below, check out this post .

  • Assignment Part III: Makefiles

Now you know how to compile C programs! This is great, but actual software projects rarely require you to invoke the compiler directly like we did so far. Often (e.g., in the CS 300 projects!) you need to compile many source files and use specific sets of flags. It’s very easy to forget a flag or source file, and doing this all by hand on the command line is time-consuming. Additionally, when you have many source files (more than 2), it can be annoying to individually recompile/relink each source file when you make a change to it.

This is why the make tool was created! Running the make tool will read a file called the Makefile for specifications on how to compile and link a program. A well-written Makefile automates all the complicated parts of compilation, so you don’t have to remember each step. Additionally, they can do tasks other than just program compilation — they can execute any shell command we provide.

In this part of the lab, you will be writing a Makefile to use when compiling your reverse array program.

A Makefile consists of one or more rules. The basic structure of a Makefile rule is:

  • The target is the name of an output file generated by this rule, or a rule label that you choose in certain special cases.
  • The dependencies are the files or other targets that this target depends on.
  • The shell command is the command you want to run when the target or dependencies are out of date.
  • From gnu.org : A target is out of date if it does not exist or if it is older than any of the dependencies (by comparison of last-modification times). The idea is that the contents of the target file are computed based on information in the dependencies, so if any of the dependencies changes, the contents of the existing target file are no longer necessarily valid.
  • If a target is out of date, running make <target> will first remake any of its target dependencies and then run the <shell_command> .
  • In general, the name of the Makefile target should be the same as the name of the output file, because then running make <target> will rebuild the target when the output file is older than its dependencies.

Linking is the process of combining many object files and libraries into a single (usually executable) file. If you look at the file test_reverse.c , at the top, you can see there is an #include “reverse.h” . This is so that we can use the functions that you wrote to test them, and as you can see, reverse_arr is called in the function test_reverse . You can link these two files together with the following Makefile rule:

The target is the executable named reverse_test, the dependencies are test_reverse.c , reverse.c , and reverse.h . And to compile, instead of typing the shell command, you can just type:

This will cause the Makefile to run the reverse_test target, which will execute the command gcc test_reverse.c -o reverse_test if a reverse_test executable doesn’t exist or if the reverse_test executable is older than any of the dependencies. Notice how this only works properly if the name of the output executable is the same as the target name.

That was a lot of reading and information, but now you are ready to create your own Makefile!

  • Create an empty Makefile by typing touch Makefile in your lab directory.
  • Modify your Makefile so that it has one target, reverse_test , that will compile reverse.c and test_reverse.c .
  • Run make reverse_test to make sure it compiles successfully. (You may need to delete the reverse_test binary via rm -f reverse_test to make this work.)

Makefiles support defining variables, so that you can reuse flags and names you commonly use. MY_VAR = "something" will define a variable that can be used as $(MY_VAR) or ${MY_VAR} in your rules. A common way to define flags for C program compilation is to have a CFLAGS variable that you include whenever you run gcc. For example, you can then rewrite your target like this:

Automatic Variables are special variables called automatic variables that can have a different value for each rule in a Makefile and are designed to make writing rules simpler. They can only be used in the command portion of a rule!

Here are some common automatic variables:

$@ represents the name of the current rule’s target. $^ represents the names of all of the current rule’s dependencies, with spaces in between. $< represents the name of the current rule’s first dependency.

If we wanted to stop using test_reverse.c and reverse.c to avoid repetitiveness, we could rewrite our target like this:

Task : Use regular variables (i.e. CFLAGS ) and automatic variables simplify your Makefile and add the -O3 flag. Note : you can do MY_VAR += <additional flags> if you want to compile with more flags and only use one variable.

Phony Targets

There are also targets known as ‘phony’ targets. These are targets that themselves create no files, but rather exist to provide shortcuts for doing other common operations, like making all the targets in our Makefile or getting rid of all the executables that we made.

To mark targets as phony, you need to include this line before any targets in your Makefile:

.PHONY: target1 target2 etc.

Why do we need to declare a target as phony?

  • To avoid a conflict with a file of the same name: We learned earlier that targets will only execute their <shell_command> if the target file is out-of-date. This is problematic because phony targets generally don’t create files under the target name. If somehow there exists a file under the same name as a phony target, the phony target’s command will never be run. You can avoid this by explicitly declaring a target as phony to specify to the make tool to rebuild the target even if it’s not “out-of-date”.
  • To improve performance: there’s also a more advanced performance advantage that you can learn more about here .

Here are some common phony targets that we’ll be using in this course:

We use the all target to make all of the executables (non-phony targets) in our project simultaneously. This is what it generally looks like:

all: target1 target2 target3

As you can see, there are no shell commands associated with the all target. In fact, we don’t need to include shell commands for all, because by including each target (target1, target2, target3) as dependencies for the all target, the Makefile will automatically build those targets in order to fulfill the requirements of all.

In other words, since the all target depends on all the executables in a project, building the all target causes make to first build every other target in our Makefile.

clean target

We also have a target for getting rid of all the executables (and other files we created with make) in our project. This is the clean target.

The clean target generally looks like this:

As you can see, the clean target is fundamentally just a shell command to remove all the executables and object files that we made earlier. By convention, the clean target should remove all content automatically generated by make. It must be a phony target, because by definition, make clean doesn’t generate output files (but rather removes them)!

Note : Be careful which files you put after the rm -f command , as they will be deleted when you run make clean. Don’t put your .c or .h files because you might lose the code that you wrote!

format target

In this class, you will notice that all of the Makefiles will also contain a format target, which use a command called clang-format to style your .c and .h files following a specified standard. A typical format command would look like this:

The above command will format any listed files according to Google’s coding conventions (a set of stylistic and technical conventions that Google engineers agreed to use).

Note : When using this, keep in mind the order of your #include files. Formatting might change the order of include statements. This is something to consider if, for example, you are importing a header file that relies on standard libraries from the file you’re importing it in. To avoid this, make sure that your header files are self-contained (i.e., include all the headers they need).

check target

You’ll also notice a check target in the Makefiles we provide in future labs and projects. If you were to create a check target in this particular instance, the dependency for the check target is the reverse_test executable.

Task : Add all , clean , and format targets to your Makefile.

  • Running make without any targets will run the first target in your Makefile. Consequently, you should place the all target as the first target so that typing make will automatically generate all the executables.
  • Don’t forget to mark these targets as phony!

Simplifying Linking

It is often a good idea to break compilation of a large program into smaller sub-steps. Consider, for example, this command you used earlier:

gcc test_reverse.c reverse.c -o reverse_test

For this program, gcc creates two separate .o files, one for test_reverse.c and one for reverse.c and then links them together. But what if you had hundreds of source files?

Large vs. Small Projects: For small projects, the above works well. However, for large projects it can be much faster to generate intermediate .o files (so-called “object files”) and then separately link the .o files together into an executable. Linking is the process of combining multiple object files (which already contain machine code, but not a full program) into a full executable program.

Why does this make sense? Imagine a project that generates two shared libraries and four executables, all of which separately link a file called data.c . Let’s say the data.o file takes 1 second to compile. If you compile and link each executable in one command (without creating intermediate .o files), gcc will rebuild the data.o file five times, resulting in 5 seconds of build time. If you separately build the data.o file, you’ll build the data.c file only once (taking 1 second) and then link it (which is much faster than compiling from scratch, especially with large source files). So, if linking takes 0.2 seconds per file, the total build time will be 2 seconds instead of 5 seconds.

Although this technique won’t yield a huge performance benefit in the case of our small lab, let’s try this to drive the concept of linking home! We can then use our Makefile to automate this process for us, so that we don’t have to regenerate all object and source files every time we edit one of them.

To do this, we need to first generate object files for each file, containing the machine instructions. Then we need to link these programs together into one executable.

To create the object files without linking them, we use the -c flag when running gcc. For example, to create object files for test_reverse.c and reverse.c , we would run:

This will generate reverse.o and test_reverse.o files. Then, to link the object files into an executable, we would run:

The advantage of creating object files independently is that when a source file is changed, we only need to create the object file for that source file. For example, if we changed reverse.c , we would just have to run gcc -c reverse.c -o reverse.o to get the object file, and then gcc reverse.o test_reverse.o instead of also regenerating test_reverse.o to get the final executable.

Task: In your Makefile, create targets for test_reverse.o and reverse.o , that each include the corresponding source file as a dependency.

  • Each of these targets should compile their source file into an object file (not an executable). They also need their correct flags for optimization and debugging.
  • Update your reverse_test targets to use the .o files.
  • Update your clean and format targets.
  • Thanks to this, make will only recompile each individual object file if that file’s source was changed. It may not make the biggest difference for this lab, but in a larger project doing this will save you lots of time.

Pattern Rules

The last Makefile technique we’ll discuss are pattern rules. These are very commonly used in Makefiles. A pattern rule uses the % character in the target to create a general rule. As an example:

The % will match any non empty substring in the target, and the % used in dependencies will substitute the target’s matched string. In this case, this will specify how to make any file_<name> executable with another file called <name>.c as a dependency. If <name>.c doesn’t exist or can’t be made, this will throw an error.

As you may have noticed, both the test_reverse.o and reverse.o targets are running the same command, which means that we can simplify it.

Task : Use pattern rules to simplify your Makefile targets such that you can generate reverse.o and test_reverse.o using only one rule rather than two seperate rules. If you need help, this documentation might help.

  • Handin instructions

Turn in your code by pushing your git repository to csci0300-s24-labs-YOURUSERNAME.git .

Then, head to the grading server . On the “Labs” page, use the “Lab 1 checkoff” button to check off your lab.

Note: Lab checkoffs are tied to Git commits. So, when you check off Lab 1 (with a new commit), your grade for Lab 0 will disappear .

This is nothing to worry about! Your grade is still associated with the older commit, and if you select that commit from the dropdown on the grading server, you will be able to see the prior grade.

At the end of the semester, we will collate all lab grades across your commit history.

VTULOOP

1ST / 2ND SEM VTU C PROGRAMMING LAB | ALL IN ONE

VTULOOP

  • October 12, 2021
  • 1st Sem , 2nd Sem , C Programming Lab , VTU CSE LAB

This page includes all the C programming lab programs code, algorithm, output, VTU Viva Questions .

All these programs are according to the VTU 2018 scheme. Copy the code and test all the c programs.

Familiarization with programming environment, the concept of naming the program files, storing, compilation, execution, and debugging. Taking any simple C- code

Develop a Program to solve simple computational problems using arithmetic expressions and use of each operator leading to the simulation of a Commercial calculator

Develop a program to compute the roots of a quadratic equation by accepting the coefficients. Print the appropriate messages

Develop a program to find the reverse of a positive integer and check for PALINDROME or NOT. Display appropriate messages

An electricity board charges the following rates for the use of electricity: for the first 200 units 80 paise per unit: for the next 100 units 90 paise per unit: beyond 300 units rupees 1 per unit. All users are charged a minimum of rupees 100 as a meter charge. If the total amount is more than Rs 400, then an additional surcharge of 15% of the total amount is charged. Write a program to read the name of the user, the number of units consumed, and print out the charges

Introduce 1D Array manipulation and implement binary search

Implement using functions to check whether the given number is prime and display appropriate messages

Develop a program to introduce 2D array manipulation and implement matrix multiplication and ensure the rules of multiplication are checked

Develop a Program to compute Sin(x) using Taylor series approximation. Compare your result with the built-in Library function. Print both the results with appropriate messages

Write functions to implement string operations such as compare, concatenate, string length. Convince the parameter passing techniques

Develop a program to sort the given set of N numbers using Bubble sort

Develop a program to find the square root of a given number N and execute for all possible inputs with appropriate messages

Implement structures to read, write, compute average- marks and the students scoring above and below the average marks for a class of N students

Develop a program using pointers to compute the sum, mean and standard deviation of all elements stored in an array of n real numbers

Write a Program to Convert a Binary Number into a Decimal Number

Download C Programming Lab Manual

Related Posts

1st and 2nd sem 2022 scheme vtu notes.

  • December 7, 2023

1st and 2nd Sem 2021 Scheme Vtu Notes

Microprocessor and microcontroller lab programs | all in one.

  • December 24, 2021

Leave a Reply Cancel Reply

Your email address will not be published. Required fields are marked *

Name  *

Email  *

Add Comment  *

Save my name, email, and website in this browser for the next time I comment.

Post Comment

Trending now

lab assignment c programming

CS31 In-Lab Assignment 02: C programming and debugging with gdb

1. goals for this week:, 2. copy over example code, 3. c arrays, 4.1. reading input from a file, 4.2. running a program with command line args, 5.1. running gdb, 5.2. example gdb session, 5.3. gdb and command line arguments, 6. lab 2 intro, 7. handy resources.

Practice with a C program that uses arrays.

Practice with input in C: our read_file library to read from a file.

High-level overview of C programs with command line arguments.

Learn some basic gdb debugging commands. You should start using gdb to help with debugging your Lab 2 code. Over the course of the semester we will revisit using gdb , introducing more commands and features.

Introduce and get started on Lab 2 .

Create a In-Lab Assignment 02 directory in your cs31/inlab subdirectory and copy over some files:

Together we are going to start by looking at arrays.c , which contains examples of how to use (and abuse) arrays in C.

First, let’s open the arrays.c file in your favorite editor.

Next, let’s compile and run it next (we can use make to compile):

4. Input in C

In testfile.c are examples of reading values from a file using the C read_file library that the CS31 instructors wrote (implemented in read_file.c and read_file.h ).

Information about using the library is documented in comments in the .h file. Let’s open those both these in your favorite editor:

testfile.c has some code to open an input file and to read in an int value from the file. And read_file.h describes the functions provided by the library, some of which are called in testfile.c .

We are going to focus on read_file library functions ( read_float and read_int ) that read in two different types values from a file once the file is open.

Note that read_int and read_float need to know the memory location of where to put the values read in. We we use the & (ampersand) operator on a variable name to pass this memory location to the functions. We’ll talk much more about what that ampersand means as we build up our C programming skills in future assignments, but for the next lab assignment you will need to use & to pass the memory location of program variables to these functions.

The file read by the program is given as a command line argument when the program is run. To run the program testfile , specify the name of the input file on the command line like this:

You can list the contents and see the format of the values1 and values2 files, using the cat command:

Refering to the library interface commented in readfile.h , you can add calls to readfile library functions in the testfile.c program to read in the the next few values from an input file, which include both integers and floats. Then, compile and try running.

5. GDB intro

gdb is the gnu debugger for C and C++ programs.

Last week we used gdb as a calculator and converter, but it is normally used to help debug programs.

Over the course of the semester will will explore gdb features in more depth, but today we will learn just a few basics so that you can start using gdb to help you debug your C lab assignments.

To use the debugger, you usually want to compile your C program with the -g flag to add debugging information to the a.out file (this allows gdb to map binary machine code to C program code that the programmer understands).

The Makefile already has this rule for us, so let’s just run make .

Next, we will run the executable file inside the gdb debugger:

The first thing we get is the gdb prompt. At this point testprog has not yet started running.

We usually begin a debugging session by setting a break point at main before starting the program running in gdb. A breakpoint tells gdb to grab control at a certain point in the execution, in this case right before the first instruction in main is executed:

Next, we will enter the run command at the gdb prompt to tell gdb to start running our program:

The run command will start your program running, and gdb will only gain control again when a breakpoint is hit.

There are a few other primary gdb commands we will learn today. The first is the list command that displays the C source code around the point where we are in the execution:

list with a line number lists the source code around that line:

The next command (or just n as a shortcut) tells gdb to execute the next instruction and then grab control again:

The print command can be used to print out the value of a program variable or expression:

cont tells gdb to let the program continue running. Since we have no more breakpoints it will run until termination.

Now let’s add a breakpoint in the function mystery , and rerun the program:

The run command starts the program’s execution over from the beginning. When re-run, the breakpoint at the beginning of the main function will be hit first (and list displays the code around the breakpoint).

Let’s set a breakpoint at line 20, right before the call to mystery. Next, type cont to continue execution from breakpoint in main:

The program continues running until it reaches the breakpoint we just set at line 20 (Breakpoint 3). We can examine the program’s execution state at line 20 by printing out the argument values before the call to mystery (using print ), and then type cont to continue the program’s execution:

After continuing, the breakpoint in mystery is hit next (Breakpoint 2), let’s step through some of the mystery function’s execution, and print out some of its parameters and locals.

We can use the print command to print out expressions in the program, so let’s print out the values of the arguments passed to mystery, and type cont to run until the next break point is hit:

The where or bt command list the call stack:

When you’re done using gdb , type the command quit .

If you use gdb to help you debug the lab this week, you will need to give the run command a command-line argument that is the file name:

In general, for programs with command line arguments, simply list the arguments after the run command, for example to run with 3 command line arguments (6, 4, and hello), do the following:

We will learn more about C and gdb over the course of the semester, but these gdb basics are enough to start using gdb to help you debug your C programs.

Lets talk through the next Lab 2 Assignment , where you will implement a C program that, among other things, uses arrays, command line arguments, and reads values in from a file.

GitHub Org for CS31

Using Git for CS31

Git help page and GitHub one time setup steps

C code style guide

C programing resources

Compiling and running C programs on our system

C Compiling and Debugging Guide

Chapter 1 and Chapter 2 on C programming in course textbook

Chapter 3 on gdb

Some Useful Unix Commands

vi (and vim) quick reference

CS Department Using Unix

COL100 lecture notes: 20-21 Sem 2

  • What is computing, translation from a problem to an algorithm to a program
  • How a computer works, how is a program compiled and executed by a computer, binary numbers, computing using 0s and 1s
  • Programming in C. We are choosing C because it is close to the metal and will help you relate basic programming constructs to how they are implemented in a computer
  • The importance of good algorithms, how the same problem can be solved in different ways with different degrees of efficiency
  • Writing moderately complex programs. Planning first on paper how the program should be structured, and then coded in a programming language
  • Lab exam 1: March 27th, 3:30-5:00pm
  • Lab exam 2: April 25th, 3:30-5:00pm
  • Lab exam 3: May 22nd, 3:30-5:00pm
  • Re-minor/re-lab exam: May 29th, 3:30-4:30pm
  • Check out some interesting links on the history of computers , the Jacquard's loom , and integrated circuits .
  • The simple.c program we used to understand the storage of variables in memory, and operations on them. You can also take a look at Chapters 2 and 3 from the Kochan book.
  • The datatypes.c , places.c , and round.c programs we used to understand type casting. You can also take a look at Chapter 4 from the Kochan book.
  • The printmaxvals.c program we discussed in the tutorial to check for overflow when adding or subtracting two integer numbers.
  • If you are curious about how mathematical functions are implemented, such as the ones in sqrt.c , take a look at the glibc source code such as for the sqrt 32-bit floating point implementation on the IEEE-764 architecture . Browse around and read the comments to understand different implementation methods on different architectures and for different precisions.
  • Somebody came up with this interesting example with increment operators, and we understood what was going on by looking at the assembly language code to which the program was compiled by gcc. Details here .
  • The program we used to understand the difference between assignment and equaliy operators.
  • Variants of programs to find the maximum of three numbers: largest.c , another_largest.c , and yet_another_largest.c . You can also take a look at Chapter 6 from the Kochan book.
  • The programs we discussed in class: calculating e , maximum of a series of positive numbers , and sum of the digits of an integer . You can also take a look at Chapter 5 from the Kochan book.
  • Some additional programs for greater familiarity with loops: fibonacci series , whether a number is a palindrome or not (without using strings), printing shapes by evaluating conditions , printing a sine wave .
  • Consult Chapter 12 in the Kochan book for bit operations
  • Programs we discussed in class, to print the binary representation of a number, and the hexa representation , through bit operations. A twos-complement implementation to understand how negative numbers are stored and re-converted when to be displayed.
  • With some pointer type conversations, we can also print the floating point representation of a number. Test with loss of precision when you go beyond 2^23.
  • Floating point numbers have a representation for infinity. Check out this program with a division by zero with floats and integers - when does the program crash?
  • You can also take a look at Chapter 7 in the Kochan book
  • Programs we discussed in class, to show the histogram of a series of numbers, and to create sample inputs using random number generators . We also looked at redirection of input and output, and some nice tools like cat, head, and tail.
  • Updated program to generate random numbers with a random seed.
  • A simulation for forest fires in a 1D forest . Run the program for different values of fire and growth probabilities, and see whether the forest is able to sustain itself. Also try to simulate a circular forest, i.e. the left boundary of the forest connects with the right boundary. Later when we study 2D arrays, you can try changing the program to a 2D forest, and a circular 2D forest which is called a toroid.
  • The matrix multiplication program we understood in class.
  • The fun game of life program, with sample inputs for a glider , gun , and other shapes . If you are interested, you can check out more ANSI control characters here , note that you would substitute the escape character ^[ with \033 when using it in a printf in C, e.g. printf("\033[2J") to clear the entire screen (code is ^[[2J). You can also print in colour as described here , with common codes here .
  • Here is also a 1D version of game of life and a sample input file .
  • Strings are nothing but a 1D array of characters, terminated with a null '\0' character (decimal value 0). The program we discussed in class. ASCII codes 0..31 are control codes .
  • Also check out the initial portion of Chapter 10 from the Kochan book.
  • The simple programs we used to demonstrate passing by value in function calls, and passing by reference for manipulating arrays.
  • The GCD program we did in class, to read arguments from the command line, convert the string arguments to integers, and compute the GCD of a series of integers.
  • Also look up Chapter 8 from the Kochan book
  • Variants of the fibonacci program discussed in class.
  • Also look up Chapter 11 from the Kochan book
  • Here are the various programs we did in class. Learning to swap numbers, array elements, pointers to arrays. Pointer arithmetic . Arrays of pointers and why command line arguments are passed as an array of pointers.
  • Also look up Chapter 9 from the Kochan book
  • The student records calculation and sorting program we discussed in class. Input file for stdin redirection.
  • Also look up Chapter 17 from the Kochan book
  • An implementation of command line parsing : trim spaces, count the number of strings, allocate an array of pointers, allocate space for each string linked from the array of pointers.

We will keep fighting for all libraries - stand with us!

Internet Archive Audio

lab assignment c programming

  • This Just In
  • Grateful Dead
  • Old Time Radio
  • 78 RPMs and Cylinder Recordings
  • Audio Books & Poetry
  • Computers, Technology and Science
  • Music, Arts & Culture
  • News & Public Affairs
  • Spirituality & Religion
  • Radio News Archive

lab assignment c programming

  • Flickr Commons
  • Occupy Wall Street Flickr
  • NASA Images
  • Solar System Collection
  • Ames Research Center

lab assignment c programming

  • All Software
  • Old School Emulation
  • MS-DOS Games
  • Historical Software
  • Classic PC Games
  • Software Library
  • Kodi Archive and Support File
  • Vintage Software
  • CD-ROM Software
  • CD-ROM Software Library
  • Software Sites
  • Tucows Software Library
  • Shareware CD-ROMs
  • Software Capsules Compilation
  • CD-ROM Images
  • ZX Spectrum
  • DOOM Level CD

lab assignment c programming

  • Smithsonian Libraries
  • FEDLINK (US)
  • Lincoln Collection
  • American Libraries
  • Canadian Libraries
  • Universal Library
  • Project Gutenberg
  • Children's Library
  • Biodiversity Heritage Library
  • Books by Language
  • Additional Collections

lab assignment c programming

  • Prelinger Archives
  • Democracy Now!
  • Occupy Wall Street
  • TV NSA Clip Library
  • Animation & Cartoons
  • Arts & Music
  • Computers & Technology
  • Cultural & Academic Films
  • Ephemeral Films
  • Sports Videos
  • Videogame Videos
  • Youth Media

Search the history of over 866 billion web pages on the Internet.

Mobile Apps

  • Wayback Machine (iOS)
  • Wayback Machine (Android)

Browser Extensions

Archive-it subscription.

  • Explore the Collections
  • Build Collections

Save Page Now

Capture a web page as it appears now for use as a trusted citation in the future.

Please enter a valid web address

  • Donate Donate icon An illustration of a heart shape

C Programming Lab Manual

Bookreader item preview, share or embed this item, flag this item for.

  • Graphic Violence
  • Explicit Sexual Content
  • Hate Speech
  • Misinformation/Disinformation
  • Marketing/Phishing/Advertising
  • Misleading/Inaccurate/Missing Metadata

plus-circle Add Review comment Reviews

1,720 Views

DOWNLOAD OPTIONS

For users with print-disabilities

IN COLLECTIONS

Uploaded by Dipendra Bahadur Chand on June 10, 2018

SIMILAR ITEMS (based on metadata)

Master Programming

C Programming Exercises With Solutions (PDF)

Here you will get C Programming Exercises With Solutions.

Here I am going to provide you a document of these, which you can download and make changes according to your own.

C Programming Exercises With Solutions

Download C Programming Practical Assignments Questions

Download Now

Download C Programming Exercises With Solutions PDF (2020)

Download c programming exercises with solutions pdf (2021), download 100+ c programming exercises pdf.

If you face any problem in downloading C Programming Practical Assignments Questions and Solutions PDFs, then tell us in the comment below and you will definitely share this post with your friends so that they can also be of some help.

Also Read -:

  • Database Management System Lab Assignment Exercise Question and There Solutions
  • C++ Lab Exercises And Solutions (PDF) – Master Programming
  • Web Technology Lab Assignment Question And Solutions
  • Pc Software Practical Assignment Question And Solutions
  • Operating System with Linux Lab Assignment Question And Solutions (PDF)

' src=

Jeetu Sahu is A Web Developer | Computer Engineer | Passionate about Coding and Competitive Programming

You have some advantages over the ICS 22 students who are doing this problem: You're doing it with pair programming, rather than solo; you've spent a week (last quarter) thinking about machine-level programming, so the concepts in this assignment will be familiar; and you're coding in Python, which is higher-level than Java (meaning that it does more of the work for you).

Then one day, my teacher asked me if I wanted to learn how to write my own programs. I thought it sounded like a great idea. So I picked up a book about a language called BASIC — some of you may have played with it before — and typed in a short program that asked a user for a number of hits and a number of at-bats and printed out a batting average. (Believe it or not, my mother still has a printout of it, including the comment at the top: "My first program, by Alex Thornton." Yes, I commented my first program.) I ran the program, tried it out, and I was mesmerized; the computer did exactly what I asked it to, exactly the way I asked it to. And my lifelong obsession with what I would later know to be computer science began.

BASIC was a good teaching tool for its day: versatile and easy-to-learn. For this project, we've designed a simpler version of BASIC called Facile, which supports only eleven kinds of statements. You'll be building a Facile interpreter , to read and execute Facile programs.

The Facile language: We'll discuss the requirements for your interpreter later in the write-up. First, let's talk about the Facile language. A Facile program is a sequence of statements , one per line. Here's an example of a Facile program:

Each line contains exactly one statement (i.e., there may be no blank lines). Facile assigns a line number to each of the lines, where the first line of the program is numbered 1, the second line is numbered 2, and so on. The last line of the program is a period (.) on a line by itself. Execution of a Facile program always begins at line number 1. There is no predefined limit on the number of lines in a Facile program.

Variables: A Facile program has variables, each named by a sequence of characters that does not include whitespace. Each variable is capable of storing an integer value.   The value of a variable may be assigned or changed with a LET statement. A LET statement changes the value of one variable. Some examples are:

  • LET A 3 — changes the value of the variable A to 3
  • LET Z –9 — changes the value of the variable Z to –9

You can print the value of a variable to the console by using a PRINT statement. A PRINT statement prints the value of one variable, followed by a newline. So, consider the following short Facile program:

Its output would be:

Execution of a Facile program: A Facile program is executed one line at a time, beginning at line number 1. Ordinarily, execution proceeds forward, so that line 1 will execute first, followed by line 2, followed by line 3, and so on. Execution continues until either an END statement is reached, or until it reaches the "." line that appears at the end of the program.

As in any programming language, it is possible in Facile to write programs that execute out of sequence, though the mechanisms are a bit more primitive than they are in a language like Python. A GOTO statement causes execution to "jump" immediately to the given number. For example, the statement GOTO 4 jumps execution to line 4. Here's an example Facile program that uses GOTO:

In this program, line 1 is executed first, setting the variable A's value to 1. Then the GOTO statement will immediately jump execution of the program to line 4, skipping the second LET. Line 4 prints the value of A, which is 1. So, the output of the program is 1.

A GOTO statement may jump either forward or backward, meaning that the following program is a legal Facile program. See if you can figure out what its output would be.

GOTO statements are not permitted to jump beyond the boundaries of the program, to lines before line 1 or lines after the "." that completes the program. If such a GOTO statement is encountered while a program is executed, the interpreter terminates with an error message.

Mathematical operations: Facile provides the typical mathematical operations that can be performed on variables: addition, subtraction, multiplication, and division. Each operation is provided as a statement that changes the value of the given variable. Here are examples of their use:

In the example above, the ADD statement adds 3 to the value of A, storing the result in A. So, printing A will display 7 on the console. The output of the program above is:

It is important to note that, since all variables in Facile are integers, the DIV statement implements integer division, meaning that its result is the floor (or integral part) of the quotient. So, in the example above, 7 / 2 = 3. [Note: Python has an integer division operator.] The second operand may not be zero, meaning that the statement DIV A 0 is illegal. When a Facile program encounters a division by zero, it immediately terminates with an error message.

The IF statement: Facile provides an IF statement, which acts like a conditional GOTO. It compares the value of some variable to some value, and jumps execution of the program to the given line number if the comparison is true. The comparison can use one of the typical relational operators: <, <=, >, >=, = (equal to), or <> (not equal to).

In the program above, the variables A and B are given the values 3 and 5, respectively. An IF statement then compares A to 4. Since A is less than 4, execution jumps to line 5. B's value is printed out. So this program's output is simply a line containing 5.

The IF statement in Facile is substantially less flexible than its Python equivalent. In an IF statement in Facile, the token IF must be followed by exactly five tokens. The first must be the name of a variable. The second must be one of the relational operators (<, <=, >, >=, =, or <>). The third must be an integer constant. The fourth must be the word THEN. The fifth must be a line number. They behave in the way you might expect. For example: IF C <> 0 THEN 4 means "jump to line 4 if C is not equal to 0".

Like GOTO statements, IF statements are not permitted to jump beyond the boundaries of the program. An attempt to do so should cause the Facile program to terminate with an error message.

Subroutines: There are no methods or functions in Facile, but there is a simplified mechanism called a subroutine . A subroutine is a chunk of Facile code that can be "called" by issuing a GOSUB statement. GOSUB is much like GOTO; it causes execution to jump to a particular line. However, GOSUB also causes the Facile program to remember where it jumped from. Subsequently, when a RETURN statement is reached, execution continues at the line following the GOSUB statement that caused the jump. Here's an example:

In the program above, line 1 is executed first, setting the value of A to 1. Next, a GOSUB statement is reached. Execution jumps to line 6, but Facile also remembers that when a RETURN statement is reached, execution should jump back to the line following the GOSUB — in this case, line 3. Line 6 is executed next, setting A to 2, then line 7 sets B to 3. Now we reach a RETURN statement, causing execution to jump back to the line number that we're remembering — line 3. Line 3 prints the value of A (which is 2), then line 4 prints the value of B (which is 3). Next, we reach line 5, which is an END statement, so the program ends.

Subroutines can be used very similarly to Python functions, except they do not take parameters or return a value. Consider the following example, which contains a subroutine that prints the values of A, B, and C each time it's called:

Subroutines may call other subroutines, meaning that two or more GOSUB's may be reached before a RETURN is reached. The rules for this are very similar to methods that call other methods in Python; for each GOSUB that is reached, Facile will remember the line to which it should return. When a RETURN is reached, execution will move to the line remembered from the most recent GOSUB. Here's an example:

In this example, execution begins at line 1 by setting the variable A to 1. Next, we jump to line 7 with a GOSUB, remembering that we should jump back to line 3 when we encounter a RETURN. Line 7 prints A (which is 1), then line 8 changes A's value to 2. Now we've reached line 9, which is another GOSUB statement. At this point, execution will jump to line 5, but we'll also need to remember to jump back to the line following this GOSUB — line 10 — when we reach a RETURN. But we also need to remember the line from the previous GOSUB — line 3.

Line 5 sets A to 3, then we encounter our first RETURN statement. We're remembering two lines — line 3 and line 10. But line 10 is the most recently remembered line, so execution jumps to line 10. Line 10 prints A (which is 3). Now we encounter another RETURN statement on line 11. We're remembering the line 3 from the first GOSUB. So execution jumps to line 3, printing A (which is still 3), then ending the program on line 4.

So, the output of this program is:

Like GOTO statements, GOSUB statements are not permitted to jump beyond the boundaries of the program, to lines before line 1 or lines after the "." that completes the program. If such a GOSUB statement is encountered while a program is executed, the interpreter terminates with an error message.

It is also an error for a RETURN statement to be encountered when there has been no previous GOSUB. The Facile program will immediately terminate and print an error message in this case, as well.

Whitespace: While Facile programs may not have blank lines in them, the amount and placement of blank space between the words on each line is considered irrelevant. So, the following is a legal Facile program:

Experimenting with Facile: An interpreter is a program that is capable of executing a program written in some programming language. To give you the ability to experiment, we've implemented a Facile interpreter for Windows already. (For those of you who don't ordinarily use Windows, remember that our machines in the ICS labs run Windows, so you'll have ample opportunity to experiment with Facile. You might even want to "pair program" while you experiment.) This Zip archive contains the interpreter (Facile.exe) and most of the Facile programs that appear in this write-up, along with a few additional ones that demonstrate fatal errors (division by zero, a RETURN statement without a corresponding GOSUB, and a GOTO to a non-existent line). Feel free to write your own, as well. Unzip the archive into one folder, then double-click the program. From there, it's fairly self-explanatory. A word of warning about this interpreter: we wrote it without making a serious attempt at handling syntax problems, so it assumes that the input file is a legal Facile program. If you attempt to run an input file that is not legal Facile, you may see the message "ERROR IN PROGRAM", but it's also possible that my interpreter may simply crash.   Moreover, this interpreter's version of Facile has a few subtle differences from the version described here:   In particular, it recognizes only the 26 variables A through Z, and it initializes all of them to 0 automatically.

We're providing this interpreter so you can experiment with the language as you have questions about it. Once you're comfortable with it, it'll be your turn to implement a Facile interpreter. (Bear in mind that this Facile interpreter implements much of the optional work described in the "Additional challenges" section below, but it will behave correctly on the samples given in this write-up.)

Your program: For this project, you'll be building your own Facile interpreter , which is a program that is capable of executing a Facile program, generating the correct output according to the specification in the previous sections. Since you're already familiar with Python, you'll write your Facile interpreter in Python. (Since Python runs on many operating systems, that means, once completed, you'll be able to use your interpreter to run Facile programs on Windows, Mac OS X, Linux, Unix, and several other platforms.)

The Facile interpreter that we've provided runs in a (very simple) graphical user interface. Your program, on the other hand, should read one Facile program from an input file, then execute it, writing any output from the Facile program to the console (i.e., using print statements).

As a starting point, we will discuss the organization of this program in class.   Of course you may use the code we develop as a basis for your complete solution.

How an interpreter works: A typical interpreter will execute a program one statement at a time, keeping track of what we might call the program state as it goes along. In the case of a Python interpreter, you might imagine that there would be quite a bit of work to be done. The interpreter would need to keep track of all of the objects — creating new ones and garbage-collecting old ones as necessary — as well as maintain the "call stack," along with various other tasks required by Python programs. Implementing an efficient, complete Python interpreter is a project that would easily take many programmer-years.

A Facile interpreter is a much simpler program, since Facile is a much simpler programming language. Your interpreter will need to execute a Facile program one statement at a time, updating the program state as necessary, until either an END statement or the "." is reached. (The "." can simply be treated as an END statement, if you'd like.) The program state consists of the following information:

  • what line of code is currently executing (you might call this the program counter, which you may remember from the Deus X machine)
  • the integer value in each of the variables
  • the call stack; that is, the line numbers remembered because of any GOSUB statements (Since each RETURN jumps back to the line following the most recent GOSUB, it makes sense to store these line numbers in a stack.)

Each statement has a different effect on the program state. For example, a LET statement will cause the value of one of the variables to change, then cause the program counter to be incremented (since, after a LET statement, execution continues on to the next statement), a GOTO statement will cause the program counter to be changed to the line number specified in the statement, and so on.

Reading the program and representing it in memory: Your program will need to begin by reading the Facile program from an input file and representing it in memory. There are a number of ways to solve this problem. One way is to read the program into memory as a collection of strings, with each of the strings containing one line of the input program. Every time a particular line is executed, it would need to be parsed (to see what kind of statement it was), then executed. As you might imagine, this is a terribly inefficient way to implement an interpreter, since the same statement may need to be parsed over and over again. You are not permitted to use this approach for your interpreter.

A better approach — one that we're requiring you to use instead — is to read the input program once, parse it once, and represent it as a list of statement objects. The object-oriented programming concept of inheritance provides a very natural design approach for this problem.

  • A base class called Statement contains any functionality common to all statements. The only common functionality for all statements is that they can be executed, though what happens when they are executed is different depending on the type of the statement. We can represent this in the Statement class with a method called execute() . A Statement object might also contain a list containing whatever arguments appeared after the keyword.
  • For each kind of statement (e.g., LET, PRINT, etc.), a subclass of Statement can be designed (e.g., LetStatement, PrintStatement). Each subclass will inherit the field listing the arguments — the information needed to execute the statement. In the case of a LET statement, for example, the necessary information is the name of the variable and the value to assign into it. Also needed in each of these Statement subclasses is an actual implementation of the execute() method.  

You'll need code that can parse the input file and create the appropriate sequence of Statement objects, reading the input file and returning a list of Statement objects (actually, Statement subclass objects) containing all the statements in the program. Note that line numbers in Facile start at 1, not 0, so we suggest storing None as the first element in the list, then storing the actual Statement objects with indices beginning at 1. (An alternative, storing the statements beginning at index 0, will require the error-prone practice of adding or subtracting one when converting between line numbers and list indices, which can easily lead to chaos.)

You may assume that the input file contains a syntactically legal Facile program. It's acceptable for your program to either print an error message, ignore lines that aren't understood, or even crash in the event that it's given an input file that is not legal Facile. (It's a good thing Python interpreters don't behave this way.) We will only test your interpreter with syntactically legal Facile programs, though the programs may have run-time errors in them. As was discussed above, there are three kinds of run-time errors: division by zero, a RETURN statement without a corresponding GOSUB, and a GOTO/GOSUB/IF..THEN to a line outside of the boundaries of the program. Your interpreter will need to behave reasonably in these cases, by printing a meaningful error message and terminating gracefully.

Designing your interpreter: As the size of a program increases, one of the most difficult obstacles that programmers face is the need to "separate their concerns." One of the primary strategies that programmers use to separate their concerns is to break a large program into a set of smaller pieces. The obvious mechanism for breaking up a program in an object-oriented language is the use of classes.

Separating concerns is something novice programmers need to learn. The temptation is always to try to think about the complete picture, since this strategy works well for the short programs that you write when you're first starting out. As programs become larger, confusion naturally sets in, as the complete picture can be difficult to keep in your brain all at once. Even moderately small programs can be built out of many classes and encompass a great deal of complexity. This project will encourage you to begin thinking about your programs the same way, which will give you the ability to write much larger programs than you could before.

The main tasks that your program must perform are:

  • Read the contents of the input file, parsing each line, and storing an object into memory that represents the Facile statement appearing on that line.
  • Create a representation of the initial program state, then begin executing the program one statement at a time. The execution of each statement will cause the program state to be changed, and may also cause output to appear on the console.
  • Continue executing the program until an END statement or the "." is reached.

We suggest breaking up your program in the following way:

  • Main program . This will oversee the execution of the interpreter on one input file. Interpretation requires following the sequence of steps above: parsing the input file, creating an initial program state, then executing one statement at a time until the program ends. Most of the actual work is delegated to other classes, with Interpreter acting as a manager.
  • Parser . This parses the input file and returns a list of objects representing statements.
  • CallStack . A generic stack (which you can implement easily with a list). You'll use this to store the return points from GOSUB statements.  
  • ProgramState . This represents the state of an executing Facile program. It contains the program counter, the values in each of the variables, and the call stack.
  • Statement . This class represents a Facile statement. Subclasses such as LetStatement, PrintStatement, etc., implement the actual statements.

It's a good idea to build as many of the underlying pieces as you need to implement a couple of the statements, say LET and PRINT, first. Afterwards, add new kinds of statements one or two at a time, making any changes required in the underlying pieces.

Facile quick reference: Here is a list of all of the Facile statements that should be supported by your interpreter, with a brief description of the effect of each. In each of the statements below, var may be the name of a variable, int may be an integer constant (e.g., 1, –3, 15), and linenum may be a line number (1 or greater).

Additional challenges: Your Facile interpreter may implement some additional features; these are not required.Here are two additional statements:

Including these statements in Facile does not dramatically increase its power, but it does allow for convenient incrementing and decrementing, which can be handy for constructing simple "loops."

Another improvement to the interpreter can increase the expressiveness of the language quite a bit: Consider a statement such as LET. As defined above, the LET statement sets the value of some variable to some integer constant. But imagine that you wanted to set the value of some variable to be equal to the value of some other variable. Facile, as defined above, does not allow this fundamental operation. But there's no reason it couldn't.

In many places where an integer constant may normally appear in a Facile program, your interpreter could also allow the name of a variable to appear. In the case of PRINT, you could also allow an integer constant instead of a variable name. So, for example, these statements may be given to the interpreter:

  • LET A B — Sets the value of A to be equal to the value of B.
  • PRINT 3 — Prints the integer constant 3 to the console.
  • ADD B C — Adds the value of C to the value of B, storing the result in B.
  • SUB B C , MULT B C , DIV B C — similar to the ADD statement above
  • IF A <= B THEN 4 — Jumps to line 4 if A is less than or equal to B.
  • IF 3 <= B THEN 4 — Jumps to line 4 if 3 is less than or equal to B.
  • IF 4 <= 9 THEN 4 — Jumps to line 4 is 4 is less than or equal to 9.

You might also consider designing and implementing some new statements to accomplish some of these important goals, or others of your own choosing:

  • Allow Facile programmers to put comments into their code. (This could bring up an interesting question about the design of the language: how should line numbers be counted if not all lines contain code?)
  • Define additional variables that can store string values instead of integers. The BASIC language names such variables with trailing $ characters. So you might have the variables A$, B$, C$, ...,   each of which is capable of storing a string.
  • Add a statement, or perhaps a variant of the PRINT statement, to output a string of text (a string literal or the value of a string variable) to the console.
  • Allow the IF statement to compare two string variables, or to compare a string variable to a string literal.
  • Add a statement to read an integer and/or string from the console and store it in a variable.
  • Add a LABEL statement that takes a variable name whose value will be the next line number in the program.   Then that variable could be used as the target for a GOTO or IF statement, saving you from having to count lines.   (Even though it occurs in your code, LABEL isn't an executable statement at all; your parser needs to handle it once, when parsing.)

Finally, here's one more sample program. You can run it in Alex's Facile interpreter; you can read it to see what it does; you can use it to test your own interpreter (but it does require that you implement one of the optional features—two if you count comments).

  • Submit all your Python source code in one .py   file via Checkmate. Each pair should submit just one solution with both partners' names clearly indicated.
  • The usual   grading criteria   for lab assignments apply.
  • Fill out a partner evaluation at EEE.

Browse Course Material

Course info, instructors.

  • Daniel Weller
  • Sharat Chikkerur

Departments

  • Electrical Engineering and Computer Science

As Taught In

  • Programming Languages
  • Software Design and Engineering

Learning Resource Types

Practical programming in c.

Solution files for a lab assignment to implement John Conway’s Game of Life as a C program. (This ZIP file contains: 1 .txt file, 1 .h file and 4 .c files.)

facebook

You are leaving MIT OpenCourseWare

  • Study Guides
  • Homework Questions

Written Assignment for Lab 8 (Programming II)

  • Electrical Engineering

COMMENTS

  1. C programming Exercises, Practice, Solution

    C is a general-purpose, imperative computer programming language, supporting structured programming, lexical variable scope and recursion, while a static type system prevents many unintended operations. C was originally developed by Dennis Ritchie between 1969 and 1973 at Bell Labs.

  2. C Exercises

    The best way to learn C programming language is by hands-on practice. This C Exercise page contains the top 30 C exercise questions with solutions that are designed for both beginners and advanced programmers. It covers all major concepts like arrays, pointers, for-loop, and many more. So, Keep it Up!

  3. Assignments

    (This ZIP file contains: 1 .txt file and 2 .c files.) 6b Function pointers, hash table (This ZIP file contains: 1 .txt file and 2 .c files.) 7 Using and creating libraries, B-trees and priority queues (This ZIP file contains: 2 .c files and 1 .db file.)

  4. C All Exercises & Assignments

    Write a C program to check whether number is positive, negative or zero . Description: You need to write a C program to check whether number is positive, negative or zero. Conditions: Create variable with name of number and the value will taken by user or console; Create this c program code using else if ladder statement

  5. Labs

    This section provides the lab assignments, supporting files, solutions, and a description of the final project for the course. Browse Course Material ... assignment_turned_in Programming Assignments with Examples. Download Course. Over 2,500 courses & materials Freely sharing knowledge with learners and educators around the world.

  6. Problem Set 1

    Problem set on writing, compiling, and debugging C programs, preprocessor macros, the C file structure, variables, functions and program statements, and returning from ... assignment_turned_in Programming Assignments with Examples. Download Course. Over 2,500 courses & materials Freely sharing knowledge with learners and educators around the ...

  7. Lab 1: Writing and Debugging C Programs

    Here's how to debug a C program using the GDB Debugger. # compile your C program using the `-g` flag to compile with debugging info. $ gcc name_of_program.c -g -o name_of_executable. # run the executable in gdb. $ gdb name_of_executable. # set a breakpoint at a function. (gdb) b name_of_a_function.

  8. Lab 1: Introduction to C

    Note that the src/qsort.c program makes use of generic void* pointers. Normally, C allows assignments only between pointers of the same type. A generic pointer in C is declared as a void pointer in C and it can be assigned to a pointer of any type. Once again, generic pointers are useful for implementing data structures, which we will explore ...

  9. C++ Lab Assignments

    C++ Lab Assignments. You can not learn a programming language by only reading the language construct. It also requires programming - writing your own code and studying those of others. Solve these assignments, then study the solutions presented here. Variable, Operator & Expression. Set 1. Set 2. Set 3. Flow of Control.

  10. PDF Programming with C

    Programming with C - Lab Prepared by IT & CSE Page 3 WEEK - 1 1 Fundamentals of Computer Hardware 2 Introduction to Programming Languages & Translators 3 DOS/UNIX Commands Fundamentals of Computer Hardware Introduction to Computers The term "Computer" is derived from the word 'compute', which means to calculate.A

  11. CS107 Lab 1: C programming under Unix

    Lab 1: C programming under Unix. Lab sessions Mon Jan 16 to Thu Jan 19. Lab written by Julie Zelenski. During lab, you will experiment and explore, ask and answer questions, and get hands-on practice in a supported environment. The lab exercises revisit topics from recent lectures/readings and prepare you to succeed at the upcoming assignment.

  12. CS 159

    Lab assignments are to be completed collaboratively in your assigned lab groups and each of these lab programming assignments will be due 30 minutes prior to the next time your lab section meets (see schedules later in this document). • Collaborative groups are expected to communicate who will submit the assignment, when the assignment will be

  13. Lab 1: C Programming and Makefiles

    Assignment Part I: C Programming. In this part of the lab, you will be writing a program that will reverse an array of strings (or, as they are known in C, char pointers). You will be writing two functions in the file reverse.c and you will test your implementation with the code found in test_reverse.c.

  14. Practical Programming in C

    This course provides a thorough introduction to the C programming language, the workhorse of the UNIX operating system and lingua franca of embedded processors and micro-controllers. The first two weeks will cover basic syntax and grammar, and expose students to practical programming techniques. The remaining lectures will focus on more advanced concepts, such as dynamic memory allocation ...

  15. 1st / 2nd Sem Vtu C Programming Lab

    VTULOOP. October 12, 2021. 1st Sem, 2nd Sem, C Programming Lab, VTU CSE LAB. This page includes all the C programming lab programs code, algorithm, output, VTU Viva Questions. All these programs are according to the VTU 2018 scheme. Copy the code and test all the c programs. Program-1. Program-2.

  16. CS 31 In-Lab Assignment 02

    Practice with a C program that uses arrays. Practice with input in C: our read_file library to read from a file. High-level overview of C programs with command line arguments. Learn some basic gdb debugging commands. You should start using gdb to help with debugging your Lab 2 code.

  17. COL100 lecture notes: 20-21 Sem 2

    Key reference book for C programming: Programming in C, by Stephen G. Kochan. ... Lecture hours: Mondays and Thursdays, 9:30-11:00am, delivered on Impartus. Tutorials: TBD Lab details The lab assignments and lab schedule can be found here. Assignments will be given weekly. We will use Piazza (class code = COL100) for discussions and Gradescope ...

  18. C Programming Lab Manual : Saral Notes

    C Programming Lab Manual_daisy.zip . Generate. C Programming Note_daisy.zip . Generate. Lab Assignment_daisy.zip . Generate. Lab Question - Bhupendra Saud_daisy.zip . For users with print-disabilities. download 5 files . EPUB . Uplevel BACK Generate. C Lab Works.epub ...

  19. C Programming Exercises With Solutions (PDF)

    Here you will get C Programming Exercises With Solutions. Here I am going to provide you a document of these, which you can download and make changes according to your own. Contents hide. 1 Download C Programming Practical Assignments Questions. 2 Download C Programming Exercises With Solutions PDF (2020)

  20. Lab 1: Game of Life

    Lab assignment to implement John Conway's Game of Life as a C program. Browse Course Material Syllabus Calendar Lecture Notes Labs Assignments Course Info ... Lab assignment to implement John Conway's Game of Life as a C program. Resource Type: Labs. pdf. 143 kB

  21. C Programming Lab Practical progams

    Write a C program to multiply two numbers (4 and 5) and display its product. Write a C program to calculate area and circumference of a circle. Write a C program to perform addition, subtraction, division and multiplication of two numbers. Write C program to evaluate each of the following equations. (i) V = u + at.

  22. Lab Assignment C

    A lab assignment for Informatics 42, a second-quarter course in computer science for Informatics majors in the Bren School of Information and Computer Sciences, UC Irvine. ... This is a pair programming assignment; do it with someone you haven't worked with yet this quarter and make sure Joel knows whom you've paired with. The problem: ...

  23. lab01_sol

    Solution files for a lab assignment to implement John Conway's Game of Life as a C program. (This ZIP file contains: 1 .txt file, 1 .h file and 4 .c files.)

  24. Written Assignment for Lab 8 (Programming II)

    Electrical-engineering document from University of the People, 5 pages, Output (Before debug) Problem 1 Solution To fix this, simply remove the else statement. Problem 2 Solution This should be a j instead of an i. Problem 3 Solution The 1 should be an i. Problem 4. Solution Instead of pos > 0 it should be pos >= 0 Problem