W2 - Shorts 📚🔍- Compiling, Debugging, Arrays, Command Line Arguments and Exit Statuses in C
Read more here
CS50 IDE: https://cs50.dev/ (Online VSCode)
CS50 Library Documentation: https://manual.cs50.io/#cs50.h
#CS50x #C #Computer_Science #VisualStudioCode
Table of Contents:
A) Introduction to Functions: Writing, Declaring, and Using Functions in C 🎦
Video: https://youtu.be/n1glFqt3g38
Functions are essential tools for writing efficient, modular, and maintainable code.
This guide introduces the concept of functions in C, their purpose, and how to implement and use them effectively, culminating in a practice problem about validating triangles.
A.1) What Are Functions?
A function is a self-contained block of code designed to perform a specific task. Functions:
- Take inputs (arguments or parameters).
- Perform an operation using these inputs.
- Return a single output (or no output, in some cases).
Functions abstract complex tasks into manageable black boxes: you only need to know what the function does, not how it does it.
A.2) Why Use Functions?
Functions provide the following benefits:
- Modularity: Break down large programs into smaller, manageable parts.
- Reusability: Write a function once and reuse it in different parts of your code or across projects.
- Debugging: Debug smaller chunks of code instead of searching through thousands of lines.
- Clarity: Make code more readable by assigning meaningful names to logical blocks.
A.3) STEP1 - Declaring a Function
Every function declaration in C has three main parts:
- Return Type: Specifies the type of value the function will return.
- Name: Describes the purpose of the function.
- Argument List: Lists the inputs the function will take, including their types.
Parts of a Function Declaration:
Example:
int addTwoInts(int a, int b);
- Return Type:
int
(returns an integer). - Name:
addTwoInts
. - Argument List: Takes two integers (
int a
andint b
).
Note: place the function prototype at the top of the main
to tell the compiler that you will use a function.
A.4) STEP2 - Defining a Function (logic and return output)
After declaring a function, define its behavior in the function body. The body includes:
- The logic for processing inputs.
- A
return
statement (if applicable) to specify the output.
Example:
int addTwoInts(int a, int b) {
return a + b;
}
A.5) STEP3 - Calling a Function
To use a function, pass appropriate arguments matching the types in its declaration. The function will return a value (if specified), which can be stored or used directly.
Example:
int x = 5, y = 10;
int sum = addTwoInts(x, y);
printf("The sum is %d\n", sum); // Output: The sum is 15
A.6) Note: Functions with No Inputs or Outputs (void)
- No Inputs: Use
void
in the argument list.void sayHello() { printf("Hello, World!\n"); }
- No Outputs: Use
void
as the return type.void printSum(int a, int b) { printf("The sum is %d\n", a + b); }
A.7) Practice Problem: Validating a Triangle
Write a function validTriangle
that:
- Takes three real numbers (sides of a triangle) as inputs.
- Returns
true
if the sides form a valid triangle,false
otherwise.
Rules for Valid Triangles
- All sides must be positive.
- The sum of any two sides must be greater than the third side.
Function Declaration
bool validTriangle(float x, float y, float z);
Function Definition
bool validTriangle(float x, float y, float z) {
if (x <= 0 || y <= 0 || z <= 0) {
return false; // Sides must be positive
}
if (x + y <= z || x + z <= y || y + z <= x) {
return false; // Triangle inequality rule
}
return true; // Valid triangle
}
Using the validTriangle
Function - Example Program:
#include <cs50.h>
#include <stdio.h>
#include <stdbool.h>
bool validTriangle(float x, float y, float z);
int main(void) {
float a = get_float("Enter side a: ");
float b = get_float("Enter side b: ");
float c = get_float("Enter side c: ");
if (validTriangle(a, b, c)) {
printf("The sides form a valid triangle.\n");
} else {
printf("The sides do not form a valid triangle.\n");
}
}
bool validTriangle(float x, float y, float z) {
if (x <= 0 || y <= 0 || z <= 0) {
return false;
}
if (x + y <= z || x + z <= y || y + z <= x) {
return false;
}
return true;
}
A.8) Key Takeaways about Function
- Functions Improve Modularity: Break down complex tasks into reusable units.
- Structure Matters: Use clear names and organize your declarations, definitions, and calls for readability.
- Practice Writing Functions: Start with simple functions and build up to more complex ones.
This approach will help you tackle real-world problems systematically while keeping your code clean and maintainable.
B) Understanding Variable Scope in C 🎦
Video: https://youtu.be/GiFbdVGjF9I
Key Concepts of Variable Scope:
- Scope: Defines where a variable can be accessed in a program.
- Types of Scope:
- [[Local Variables]]: Can only be accessed within the function they are declared in.
- [[Global Variables]]: Declared outside all functions and can be accessed by any function.
B.1) Local Variables Example
Consider the following example:
#include <stdio.h>
int triple(int x) {
return x * 3; // `x` is local to `triple`.
}
int main() {
int result = triple(5); // `result` is local to `main`.
printf("%d\n", result); // Prints 15
return 0;
}
Scope explained:
x
is local totriple
and cannot be accessed inmain
.result
is local tomain
and cannot be accessed intriple
.
B.2) Global Variables Example
Consider the following example:
#include <stdio.h>
float global = 0.5050; // Global variable
void triple_global() {
global *= 3; // Modifies the global variable
}
int main() {
triple_global();
printf("%.2f\n", global); // Prints 1.515
return 0;
}
Scope explained:
- The global variable
global
is accessible and modifiable by bothmain
andtriple_global
. - NOTE: This can be dangerous if some other part of our code write in the global variable, resulting in unexpected outcomes.
B.3) Local vs Global Scope: Why It Matters
- Local Variables:
- [[Passed by value]]: A copy of the variable is sent to the function.
- Changes in the callee (function receiving the variable) do not affect the caller's variable unless explicitly returned and reassigned.
Example: no effect on foo
int triple(int x) {
return x * 3;
}
int main(void) {
int foo = 4;
triple(foo); // foo is not multiplied,
//we are passing the value of foo to a copy called x
printf("%d\n", foo); // Prints 4
return 0;
}
Example: overwrite foo
int triple(int x) {
return x * 3;
}
int main(void) {
int foo = 4;
foo = triple(foo); // Updates `foo` with the returned value
printf("%d\n", foo); // Prints 12
return 0;
}
- Global Variables:
- Changes persist across all functions that access it, which can lead to unintended consequences.
B.4) Exercise: Troubleshooting Variable Scope (Step by Step)
Consider the following program,
Example with conflicting x
name:
#include <stdio.h>
int increment(int x) {
x++; // Modifies a local copy of `x` (think of it as x_incre)
return x;
}
int main(void) {
int x = 5; // Local to main (think of it as x_main)
int y = increment(x); // Passes a copy of `x_main` to `increment`
printf("x: %d, y: %d\n", x, y);
return 0;
}
In this example:
x
inmain
andx
inincrement
are completely independent.- Changes to
x
inincrement
do not affectx
inmain
.
Step-by-Step Execution:
main
declaresx
and assigns it5
.main
callsincrement(x)
, passing a copy ofx
toincrement
.increment
:- Increments its local copy of
x
to6
. - Returns
6
tomain
.
- Increments its local copy of
main
assigns the returned value (6
) toy
.- Final output:
x: 5, y: 6
. <----------
B.5) Key Takeaways about Variable Scope
- Use local variables for modular, predictable behavior.
- Use global variables sparingly to avoid unintended side effects.
- Always trace variable scope carefully to debug complex programs.
C) Introduction to Arrays in C 🎦
Video: https://youtu.be/K1yC1xshF40
C.1) What Are Arrays?
Arrays are a fundamental [[data structure]] that allow you to store multiple variables of the same data type in contiguous memory locations.
Instead of creating individual variables for each data point, arrays let you group and work with these data points efficiently.
C.2) Key Concepts of Arrays (analogy)
-
Structure:
- Arrays consist of elements of the same data type (e.g.,
int
,char
,float
). - Each element is stored at a contiguous memory location.
- Elements are accessed by their index, starting from 0 in C.
- Arrays consist of elements of the same data type (e.g.,
-
Analogy: Arrays are like a Mail Bank
- Imagine a post office wall filled with identical-sized mailboxes.
- Each mailbox (element) holds mail (data).
- You can access a specific mailbox using its number (index).
C.3) Examples: Declaring and Initializing Arrays
-
Declaration Syntax:
- Type: Data type of array elements.
- Name: Identifier for the array.
- Size: Number of elements the array will hold.
-
Examples:
-
An array of integers for student grades:
int student_grades[40];
-
An array of doubles for menu prices:
double menu_prices[8];
-
-
Initialization:
Arrays can be initialized at the time of declaration:int numbers[3] = {1, 2, 3};
If the size is omitted, the compiler infers it from the number of values provided:
int numbers[] = {1, 2, 3}; // Size is 3
the instantiation syntax, help us avoid the individual element syntax (since copy and pasting variable names is what we want to avoid),
C.4) Examples: Accessing Array Elements
-
Indexing:
- Access elements using their index:
student_grades[0] = 95; // Assign 95 to the first element
- Use indices in loops for iteration:
for (int i = 0; i < 40; i++) { printf("%d\n", student_grades[i]); }
- Access elements using their index:
-
Danger of Out-of-Bounds Access:
- Accessing an element outside the array's declared size can cause undefined behavior or segmentation faults:
int arr[5]; arr[10] = 42; // Undefined behavior
- Accessing an element outside the array's declared size can cause undefined behavior or segmentation faults:
C.5) Example: Working with Multidimensional Arrays
- Syntax:
type array_name[size1][size2];
- Example for a 10x10 Battleship board:
bool battleship[10][10];
- Example for a 10x10 Battleship board:
- While conceptually a grid, it's stored as a single contiguous block in memory.
Example: string words[2]
C.6) Example: Operations on Arrays (similar to variables, but not the same)
Assigning Values:
- Individual elements can be assigned:
numbers[2] = 7;
BUT.....
- Entire arrays cannot be assigned using
=
:int a[3] = {1, 2, 3}; int b[3]; b = a; // Not allowed in C
INSTEAD....
- Use a loop to copy element by element:
for (int i = 0; i < 3; i++) { b[i] = a[i]; }
C.7) Passing Arrays to Functions (Passing by Reference - Pointers)
-
Passing by Reference:
- Unlike other variables, arrays are [[passed by reference]] to functions.
- So, changes to the array in the function affect the original array:
void modify_array(int arr[], int size) { for (int i = 0; i < size; i++) { arr[i] *= 2; } } int main() { int nums[3] = {1, 2, 3}; modify_array(nums, 3); // nums is now {2, 4, 6} return 0; }
-
Passing by Value (How Other Variables Work):
- Non-array variables are [[passed by value]] (a copy is passed to the function).
C.7.1) Comparing: Passing Arrays to Function - int Arrays, char Arrays, and Strings
In C, passing an array to a function is mostly similar regardless of whether it's an array of int
, char
, or a "string" (a character array). Here’s a breakdown of how to pass each type:
1. Passing an int
Array
When you pass an int
array to a function, you generally do so by specifying the array as a pointer (since arrays decay to pointers when passed to functions). You also usually pass the size of the array separately since C functions do not have a way to know the size of an array just by looking at the pointer.
Example
#include <stdio.h>
void printIntArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printIntArray(numbers, size); // Pass array and its size
return 0;
}
- Declaration in function:
void printIntArray(int arr[], int size)
- Call:
printIntArray(numbers, size)
In printIntArray
, arr
is treated as a pointer to the first element of the array numbers
.
2. Passing a char
Array (Character Array)
A char
array can be used for storing strings (sequences of characters terminated by a null character '\0'
). When you pass a char
array, it’s often used as a "string" if it’s null-terminated. Here’s how you pass a char
array:
Example
#include <stdio.h>
void printCharArray(char arr[]) {
printf("%s\n", arr); // Assume arr is a null-terminated string
}
int main() {
char name[] = "Hello, World!";
printCharArray(name); // Pass the character array
return 0;
}
- Declaration in function:
void printCharArray(char arr[])
- Call:
printCharArray(name)
In this example, printCharArray
assumes arr
is null-terminated (a "string" in C terms). Note that you don’t need to pass the size for null-terminated strings because printf
with %s
will stop printing at the '\0'
.
3. Passing a "String" (Character Array) Using <string.h>
If you’re working with a string (character array) and need to manipulate or process it, you can use functions from <string.h>
. The function declarations don’t change; you still pass the character array as char arr[]
or char *arr
.
Here’s an example of passing a string to a function and using functions from <string.h>
:
#include <stdio.h>
#include <string.h>
void printStringLength(char str[]) {
int length = strlen(str); // Use strlen from string.h
printf("Length of string: %d\n", length);
}
int main() {
char greeting[] = "Hello!";
printStringLength(greeting); // Pass the string
return 0;
}
- Declaration in function:
void printStringLength(char str[])
- Call:
printStringLength(greeting)
In this example, printStringLength
calculates the length of str
using strlen
, a function from <string.h>
, which works on null-terminated strings.
> Summary of Differences
-
For
int
arrays: You typically pass both the array and its size to the function. The function treats the array as a pointer. -
For
char
arrays (as strings): You usually don’t need to pass the size if the array is a null-terminated string, as C functions likeprintf
orstrlen
rely on the'\0'
to determine the end of the string. The array is passed as a pointer tochar
. -
With
<string.h>
: You can use string functions forchar
arrays that are null-terminated. The functions in<string.h>
are designed to work with null-terminated character arrays, making it easier to manipulate them as strings.
In all cases, the array is passed as a pointer to the first element, but you may need to handle it differently based on its type and intended use.
C.7.2) Practice Problem: Understanding Passing Mechanisms
Analyze the following code:
#include <stdio.h>
void set_array(int arr[4]) {
arr[0] = 42; // Modifies the original array
}
void set_int(int x) {
x = 42; // Modifies only the local copy of x
}
int main() {
int a = 10;
int b[4] = {0, 1, 2, 3};
set_int(a);
set_array(b);
printf("%d %d\n", a, b[0]); // Output?
return 0;
}
Solution:
-
set_int
:- Modifies a local copy of
a
, soa
remains unchanged.
- Modifies a local copy of
-
set_array
:- Modifies the original array
b
since arrays are passed by reference.
- Modifies the original array
Output:
10 42
%i
vs%d
Both %d
and %i
can be used with printf
to print integers, but there are some subtle differences in how they behave in certain contexts:
%d
is specifically for decimal integers. (base 10 only)- It ensures proper formatting and type matching during output.
- If a non-integer is passed for
%d
, behavior is undefined (may result in errors or incorrect output).
EXAMPLE:
C.8) Summary: Arrays in C
- Arrays are powerful tools for handling collections of data.
- Use loops to iterate over arrays and perform operations.
- Understand the difference between passing by value and passing by reference for arrays.
- Be cautious of out-of-bounds access and the limitations of arrays in C.
D) Using Command Line Arguments in C 🎦
Video: https://youtu.be/AI6Ccfno6Pk
[[Command-line arguments]] allow users to provide inputs to a program directly when executing it from the terminal.
This differs from in-program prompts where inputs are collected during runtime.
Here's an explanation of how command-line arguments work in C...
D.1) Declaring main
with Command-Line Arguments
To handle command-line arguments, modify the main
function declaration as follows:
int main(int argc, string argv[])
-
argc
(Argument Count):
An integer indicating how many arguments were passed to the program, including the program's name itself. -
argv
(Argument Vector):
An array of strings containing the arguments passed to the program. Each element is a separate string.
D.1.2) Key Concepts of argc
and argv
-
argc
- Argument Count:- Counts the number of arguments, including the program's name.
For example:
Here,./greedy 1024 CS50
argc = 3
because:argv[0]
is./greedy
(program name).argv[1]
is"1024"
.argv[2]
is"CS50"
.
- Counts the number of arguments, including the program's name.
-
argv
- Argument Vector:- Stores the arguments as an array of strings:
argv[0] = "./greedy"; argv[1] = "1024"; argv[2] = "CS50";
- Indexes:
- The first element (
argv[0]
) is always the program name. - The last element is at
argv[argc - 1]
.
- The first element (
- Data Type:
All elements inargv
are strings, even if they look like numbers. For example,"1024"
inargv[1]
is stored as the string"1024"
, not the integer1024
.
- Stores the arguments as an array of strings:
D.2) Example: Converting Input Strings to Usable Integers (atoi()
)
Example: Understanding Input - string
vs int
If a program is executed with the following command:
./program hello 42
argc = 3
argv[0]
="./program"
argv[1]
="hello"
argv[2]
="42"
(here 42 is just astring
, the string "42
")
If you want to work with numeric values in argv
:
-
Use a function to convert strings to integers,
-
such as
atoi
(ASCII to integer), from<stdlib.h>
:int value = atoi(argv[1]);
This converts the string
"42"
inargv[1]
to the integer42
.
D.3) Common Problems when using Command Line Arguments
-
Accessing Out-of-Bounds Elements:
- If
argc = 3
:- Valid indexes:
argv[0]
toargv[2]
. - Accessing
argv[3]
leads to undefined behavior or a [[segmentation fault]].
- Valid indexes:
- Always ensure your loops and conditionals check
argc
to avoid accessing invalid indexes.
- If
-
String vs. Numeric Data:
- Remember that arguments in
argv
are strings. Operations like subtraction or addition require conversion to numeric types. Useatoi()
- Remember that arguments in
D.4) Examples: Practical Usage of CL Arguments
-
Single Argument Validation Example:
#include <stdio.h> int main(int argc, string argv[]) { if (argc != 2) { printf("Usage: ./program <argument>\n"); //This is the string that `printf` will display. It tells the user they should run the program with a specific format. return 1; } printf("Argument: %s\n", argv[1]); return 0; }
- This program checks if exactly one argument (besides the program name) was provided.
-
Multiple Arguments Example:
#include <stdio.h> int main(int argc, string argv[]) { for (int i = 0; i < argc; i++) { printf("argv[%d]: %s\n", i, argv[i]); } return 0; }
- This program prints all arguments provided at the command-line.
D.4.1) Benefits of Command-Line Arguments
- Allow users to configure program behavior at the time of execution.
- Enable automation and script integration by passing parameters directly.
- Simplify user interaction by reducing in-program prompts.
Command-line arguments are a foundational concept in C programming, offering flexibility and efficiency in how programs handle input.
Z) 🗃️ Glossary
File | Definition |
---|