12. C - YukaKoshiba/MyknowledgeDocs GitHub Wiki

C @English Version
Create Date:2025/07/19
Last Update Date:2025/07/22

Contents

C Language

Memory Management: Static/Dynamic Memory  Memory Deallocation Timing  Caveats  Best Practices 

Etiquette  main function  Command Line Arguments  Importing Libraries  Message Output  Comments 

Data Types and Variables  Data Types  Variable Declaration  Variable Output  Defining New Names (Aliases)

Arrays:
Specifying Characters in an Array  '\0' (Null Character)  Dynamically Allocating Array Memory
Creating a Buffer  Buffer Overflow Prevention Techniques

Basic Syntax:
if statement  loop statement 

OperatorsArithmetic Operators  Comparison Operators  Logical Operators

Functions  Built-in Functions available through Library Usage  Function Prototype Declaration

Pointer Operators

File Operations (I/O Operations)  WAV Operations

Header Files: Include Guard Structs  Implementation of Pointed Lists  Bitmap (BMP) Processing

C

A general-purpose programming language developed at AT&T Bell Laboratories in 1972.
It was created to develop the UNIX OS, and while it possesses features of a high-level language, it also functions as a low-level language that can perform memory management and hardware control.
It is widely used in the development of various software, including embedded device software, operating systems, games, and business systems.
Since it is independent of both the OS and the processor, it can support processors from major manufacturers like Intel, AMD, and ARM, allowing development without being limited to a specific environment.
C++ and C# are development languages that evolved from C language.

It is a low-level programming language that executes programs via a compiler.

Importance and Timing of Memory Management

Memory management in C language requires programmers to explicitly allocate and free memory when using dynamic memory, and careful handling is necessary.
Memory can be freed using the `free()` function.

Static Allocation vs. Dynamic Allocation

Normally, memory is allocated for variables when the program is compiled (static allocation).
Statically allocated variables and function call information, etc., are stored in a memory region called the "stack."
Memory grows upwards, and memory allocation and deallocation are performed automatically.

Dynamic memory allocation is a mechanism to secure necessary memory during program execution (runtime).
It is used when the required memory size changes depending on user input or program execution status.
Example: Variable-length data structures: handling dynamic data structures like lists or trees where array sizes cannot be determined in advance.
Dynamically allocated memory is assigned to a memory region called the "heap."
Memory grows downwards, and the programmer must explicitly allocate and free memory.

Memory regions are secured using functions like `malloc()` and `calloc()` during program execution.

Timing for using free() function

The timing of deallocation varies depending on the program's structure and memory usage.

  1. When memory is no longer needed
    Basically, dynamically allocated memory should be freed immediately with the `free()` function when it is no longer needed.
    For example,
    ・Memory temporarily used within a function should be freed before the function ends.
    ・If memory is allocated within a loop, it should be freed after exiting the loop.
  2. Before program termination
    All allocated memory should be freed before the program terminates. This is important to prevent memory leaks. However, depending on the OS, memory may be freed automatically upon program termination, so it can sometimes be omitted.
  3. Memory reuse After freeing memory, it is possible to reallocate memory using the same pointer variable.
    This helps in efficient memory utilization.
  4. Error handling If memory allocation fails (e.g., `malloc()` returns NULL), previously allocated memory must be freed before performing error handling.

Caveats when using free() function

  1. Double Free
    Attempting to free an already freed memory region again with the `free()` function can cause the program to crash.
    It is necessary to prevent double freeing by setting pointers to freed memory regions to NULL, for example.
  2. Pointer after free()
    After memory is freed with the `free()` function, that pointer variable becomes a dangling pointer (= invalid memory region).
    Using a pointer variable after freeing can lead to unexpected behavior.
    It is recommended to assign NULL to the pointer variable after freeing memory with the `free()` function.

Memory Management Best Practices

  • Always use memory allocation functions like `malloc()` and the `free()` function in pairs.
    If you allocate memory, always free it.
  • Use memory management tools like Valgrind to detect issues such as memory leaks and double frees.

Etiquette

  • The file extension is .c
  • All C programs **must have at least one `main` function**.
    *The `main` function is a very important function that indicates the program's execution starting point.*
  • Statements must always end with a semicolon (;).
    *Semicolons (;) are not needed at the end of blocks (function definitions, control structures like `if`, `for`, `while`), or declarations (structs, unions, enum definitions).*
  • Variable names can use letters, numbers, and `_`.
    Spaces and **special characters like $** are not allowed. The first character must not be a number.
  • String case is **sensitive**.
  • Regular expressions cannot be used; it is necessary to use substitute functions provided in libraries.
  • The `string` type is not available by default.
    If you want to use strings (= arrays of characters), you must use a `char` array.
  • Since the `string` type is not provided, functions for counting characters are also not provided.
  • Memory management must be handled by the programmer, making programs prone to crashing.
  • Programs are executed by a compiler.
    = It is necessary to compile the program before execution every time the source code is modified.
  • Since C is a low-level language, exception handling (try-catch mechanism) is not built in by default.
          Error handling must be explicitly implemented by the programmer.
          →e.g., Error checking using return values

main function

In C programs, execution begins from the `main` function.
The `main` function is the program's entry point (the starting point of program execution) and is a special function that can be called the heart of the program.

It is customary for the `main` function to **return 0 upon successful completion**.
= Write `return 0;` at the end.
This allows the program to communicate to the operating system that it has terminated successfully.
Also, if it **does not terminate successfully, it returns a non-zero value.**

There are two basic ways to write it:

1. Without command-line arguments (void)


int main(void){
  // Describe the process

return 0; // Successful termination }

There is also a way to write `int main() {// Describe the process}` without explicitly indicating `void` for no arguments, but
from the perspective of readability and maintainability, it is generally recommended to explicitly indicate `void`.

2. Receiving command-line arguments


int main(int argc, char *argv[]){
  // Describe the process

return 0; // Successful termination }

int: Indicates that the return value will be of type `int`.
The `int` data type is generally used for the return value of the `main` function to notify the operating system of the program's exit status.
(= The `main` function must always be declared with `int` as its return type.)

void: Write `void` when the `main` function does not accept arguments.

Command Line Arguments

int argc, char *argv[] : Receives command line arguments.
Command line arguments : Arguments passed at program execution.
→ Highly effective for increasing program flexibility and dynamic operation.

You can receive command line arguments by setting arguments in the main function as follows:


int main(int argc, char *argv[]) {
    // write program process
}

argc: The number of command line arguments, passed as the actual number of arguments + 1 (for argv[0]).

argv: An array of command line argument strings.
*Abbreviation for argument vector.

argv[0]: The executable filename of the program itself.
argv[1] and subsequent elements: Arguments entered on the command line following the program name are stored in order.
→ Since they are returned as strings, conversion is always required if you want to treat them as numerical values.
argv[argc]: NULL pointer (a special pointer that does not point to any memory area, points to nothing).

For example, if you pass the argument "John" to a file named myprogram.c:


// command
./myprogram John
argc = 2 (1 command line argument + 1)
argv[0] = "./myprogram"
argv[1] = "John"
This will be the result.

Note 1: To pass commands, use "./(no extension)filename command_line_argument" with spaces to separate arguments.

Note 2: If you specify an index beyond the number of arguments actually passed (e.g., specifying argv[5] in the above example),
errors are not automatically detected, which can lead to segmentation faults or other potential errors.
You must manage memory carefully yourself.

Getting Options

You can use the getopt() function to get options used in command line arguments.

getopt() function:
Parses command line arguments and, if specified options are found, retrieves the option's value.


// Set option
char *optionName = "begr"; // Accept only b,e,g,r
// Analyze command line arguments and return results
char variable = getopt(argc, argv, optionName);

Returned characters:
-1: No option exists.
? : Character not included in options.
Option character: A character matching an option was found.

When using getopt(), the order of command line arguments is defined as follows:
Filename Option InputFilePath OutputFilePath

The index of the first non-option command line argument (= input file path) is stored in the optind variable.

Stored value:
The initial value is 1 (= no non-option command line arguments exist).
It changes according to the number of arguments, and each time getopt() is called, the value of optind increases by the number of processed arguments.
When all options are processed, optind will be the position of the first non-option argument (= 1).

Importing Libraries

This is generally done using the preprocessor directive "#include ".


#include <stdio.h> // Library providing standard input/output functionality
#include <math.h>  // Libraries that provide mathematical functionality

Message Output


// import the library providing standard input/output functionality
#include <stdio.h>

int main(void){ printf("hello, world\n"); }

\n is called a control character; if it's not included, a "$" will be output at the end.
Also, including \n will print to the console on multiple lines, with \n at the end of each line.
Various control characters exist besides \n.
They are a type of escape sequence, and control characters target processes that control output.

Control Character Role Code Example Output Result
\n Newline printf("hello\nworld"); hello
world$
\b Backspace printf("space1\bspace2\n"); spacebspace2
\t Tab insertion printf("hello\tworld\n"); hello world
\r Return to start of line printf("hello\rworld\n"); world
\\ Display backslash (\) printf("\\\n"); \\
\" Display double quotation mark (") printf("\"\n"); "
\0 Null printf("\0\n"); (null)
\a Sound an alert beep during output printf("\a\n"); (Sound rings)

If you want to include variables in your message output, you need to specify format specifiers and variable names.


#include <stdio.h>

int main(void){
    string name = get_string("What's your name?\n ");
    int age = get_int("What's your age?\n ");

    printf("hello, %s\n", name);
    printf("your age: %d\n", age);
}
Format Specifier Data Type
%d or %i int type, bool type
%f float type, double type
%c char type
%s char array
(string type)
%p pointer

Comments


// Only one line of comment

/* *Multi-line comments */

Data Types and Variables

Data Types

Data Type Type Value Range
void Untyped -
char (Unsigned) Character type 0~255
signed char Signed character type -128~127
unsigned short int Unsigned short integer type 0~65535
short int (Signed) Short integer type -32768~32767
unsigned int Unsigned integer type 0~4294967295
int (Signed) Integer type -2147483648~2147483647
unsigned long int Unsigned long integer type 0~4294967295
long int (Signed) Long integer type -2147483648~2147483647
unsigned long long int Unsigned long long integer type 0~18446744073709551615
long long int (Signed) Long long integer type -9223372036854775808~9223372036854775807
float Single-precision floating-point type Minimum positive value: 1.175494e-38
Maximum value: 3.402823e+38
double Double-precision floating-point type Minimum positive value: 2.225074e-308
Maximum value: 1.797693e+308
bool Boolean true/false
uint8_t Requires 8 bits of space (i.e., 1 byte)
Stores unsigned/positive/non-negative integers
= Always a value of 0 or greater
0〜255
Sign

unsigned is a modifier applied to integer data types (int, char, etc.), meaning "unsigned."
Unsigned integers do not handle negative numbers, allowing them to handle twice the range of positive numbers compared to signed integers with the same number of bits.

Difference between int and long

Both int and long are integer types,
but the range of values they can handle differs depending on the computer architecture (e.g., 32-bit system or 64-bit system)
where the program is executed, and the compiler.

Usually 4 bytes (32 bits)4 bytes or 8 bytes (system dependent)Usually approx. ±2 billion4-byte systems approx. ±2 billion
8-byte systems approx. ±9 quintillionGeneral integer calculationsWhen larger integer values are required
int long
Size
Range
Usage
String data type

In C language, `char` type is most commonly used for handling strings.
In C language, `char` arrays are used to represent strings,
whereas in C++, `string` type is recommended for a higher level of abstraction.

For byte-unit data processing (file I/O, network communication, image processing, etc.), `uint8_t` is suitable for handling data in byte units.
When handling values within the range expressible by 8 bits, it improves memory efficiency compared to using larger integer types like `int`.
In embedded systems like microcontrollers, it is often used when dealing with specific bit patterns, such as controlling hardware registers.

Variable Declaration

Declare with "Data Type variableName = value;"
Initialization (declaration and assignment of a value simultaneously) is optional.


int num = 10;

Variable Output

Use format codes to output variable values with "%dataType, variableName".

Format Meaning
%d Decimal integer
%f Decimal floating-point value
%lf Decimal floating-point value
(Can display more digits than %f)
%c ASCII character
%s String

#include <stdio.h>

int main(int argc, char** argv){
	printf("Hello,%s. Your age is %d.\n", "Tomy", 20);
	printf("Your nickname is %c. \n", 'Tom');
	printf("%lf + %lf = %lf\n", 1.2, 2.7, 1.2 + 2.7);
    return 0;
}

Defining a New Name (Alias)

Using the `typedef` keyword, you can define a new name (alias) for an existing data type.
= It doesn't create a new data type, but gives an existing data type another name.

Basic syntax


typedef originalDataType newName;

// Allow unsigned char to be used with the name byte
typedef unsigned char byte;
// Declare a variable myByte of type byte
byte myByte = 255;

Arrays

A contiguous collection of variables of the same data type.
= In C language arrays, different data types cannot be mixed.

Characteristics of C language arrays include:
・**Static arrays:** The array size is determined at compile time, so the memory area is fixed, lacking flexibility.
・**Dynamic arrays:** Memory can be dynamically allocated at runtime using functions like `malloc`, allowing flexible size changes, but careful memory management is required.
・Being a low-level language, it requires programmers to perform memory management with an awareness of memory boundaries.
・Array data cannot be copied to another array as a whole (it's possible element by element).

Access is possible even if an index exceeding the declared number of elements is specified.
(Example: For an array with 50 elements, it's possible to access it by specifying an index like -3 or 58.)
→ This can lead to **undefined behavior** (program behavior becomes unpredictable, crashing, or returning unintended results).
→ This can be a breeding ground for attack methods called **buffer overflows**, potentially leading to system hijacking and security vulnerabilities.

Array declaration: `dataType arrayName[numberOfElements];`
Inserting value into array: `arrayName[index] = data;`


// Array declaration
int numbers[5]; // An array that can store 5 integers
// Value insertion or initialization
numbers[0] = 2;
numbers[1] = 10; 

// Array declaration and initialization simultaneously
int scores[5] = {80, 90, 75, 60, 100};

// Array declaration and initializing all elements to the same value simultaneously
int scores[5] = {10};

// Copying array data to another array
int numDatas[3] = {1, 5, 8};
int numDatasNew[3];

// Assignment like numDatasNew = numDatas is not possible
for (int i; i < 3; i++) { // Corrected loop condition from i++ to i++ after loop body
  numDatasNew[i] = numDatas[i]; // Corrected assignment direction
}

Specifying Characters within an Array

By writing it as shown below, it is possible to specify the Nth character of the value stored in an element.


// The i-th character of argv[1], which is the first command-line argument, is not '\0' (null character).
int main(int argc, string argv[])
{
  // The i-th character of argv[1] is not '\0' (null character).
  if (argv[1][i] != '\0') {
    // Write processing here
  }
}

'\0' (Null Character)

A special character used to indicate the **termination of a string**.
It corresponds to **ASCII code 0**.
When a string pointer points to a null character, it is considered the end of that string.

The `null` found in other programming languages and the C language null character (`\0`) are similar but different.
`null` has various meanings depending on the context in programming languages and databases, and generally represents a variable that has not yet been initialized or a state where a value does not exist.

`null` is a **value** that represents an undefined or non-existent value, whereas
the null character (`\0`) is a **character**, a type of character.

Dynamically Allocating Memory

**Dynamic memory**: A memory region whose size can be changed as needed during program execution.

When dynamically setting the number of array elements based on user input, etc.,
dynamically allocating memory in C language allows for
a flexible program and prevents errors.

When creating a dynamic array (dynamically allocating memory), the `malloc()` function is used.
The basic syntax is `void *malloc(size_t size);`
Therefore, if memory allocation is successful, it returns the starting address of the allocated memory region.
If memory allocation fails (e.g., due to insufficient memory), it returns `NULL`.

Specific usage is as follows:


#include <stdlib.h>
#include <string.h> // For strlen

// Dynamically set the string (array of characters) for 'text' based on user input. char input = get_string("plaintext: "); // Assuming get_string returns char char *text = malloc((strlen(input) + 1) * sizeof(char)); // Use sizeof(char)

// Finally, free the dynamic memory free(text);

Usage examples:
・**Dynamically determining array size:** When the number of array elements is determined at program execution.
・**Dynamically determining the number of structures:** When you want to increase the number of structures based on the amount of data to be processed.
・**Dynamically determining string length:** When the length of the input string is not known beforehand.

Creating a Buffer

A buffer is a memory area used to temporarily hold data.
It is primarily used to smooth the flow of data when sending, receiving, or processing data.

In C language, when performing memory allocation, it is common to allocate a buffer by creating either a static array or a dynamic array, as shown above.

In addition, there are also buffers called **ring buffers**, which cyclically use a fixed-length buffer.
They prevent data overflow and enable efficient data processing,
and are mainly used in stream data processing and real-time systems.

Tips for choosing:
・**For small, fixed-length buffers:** Static arrays are suitable.
・**For large buffers or variable-sized buffers:** Use dynamic arrays.
・**For stream data processing or real-time systems:** Ring buffers are effective.
When considering security, be aware of buffer overflows and take countermeasures.

Techniques to Avoid Buffer Overflow

Use the `fgets` function instead of the `scanf` function.
This is a very important technique to avoid the security issue of buffer overflow in C language.

Problems with the `scanf` function:
The `scanf` function is used to read user input in a specified format.
However, the `scanf` function has a major drawback: it cannot properly limit the size of the input data.
For example,

if there was a code like this,
if the user enters a string longer than 10 characters, it will write beyond the `buffer`'s memory area.
This phenomenon is called **buffer overflow**, and it can lead to program crashes or, in the worst case, the execution of malicious code.

Advantages of the `fgets` function:
On the other hand, the `fgets` function reads a string from a specified file stream into a buffer of a specified size.
Using the `fgets` function allows you to limit the size of the data being read, thus preventing buffer overflow.
Example: `char buffer[10]; fgets(buffer, sizeof(buffer), stdin);`
In this example, the `fgets` function will not read data beyond the size of `buffer`.

Basic Syntax

If Statement


#include <stdio.h>

int main(void) { int x = 1; int y = 2;

// Check whether agreed (this comment seems out of context for the code)
if (x > y) { // Corrected Y to y
    printf("x is greater than y.\n");
} else if (x == y) {
    printf("x is equal to y.\n");
} else {
    printf("x is not greater than y.\n");
}

}

Loop Statement


#include <stdio.h>
int main(void)
{
  for (int i = 0; i < 3; i++) {
    printf("Hello!\n");
  }
}

#include <stdio.h>

int main(void) // main function needs to be defined
{
  int i = 3; // Declared i inside main
  while (i > 0)
  {
    printf("hello!\n");
    i--;
  }
  return 0; // Added return statement for main
}

Operators

Arithmetic Operators

Precedence of calculation process executed from top to bottom

Operator Description Usage
* Multiplication 3 * 2 →6
'3' * 2 →'333'
/ Division
If both operands are integers: the result will also be an integer
= Decimal part is truncated
If one of the operands is a floating-point type: the result will also be a floating-point number
% Modulo (calculates the remainder) 22 % 8 →6
+ Addition 3 + 2 # 5
'3' + '2' →'32'
- Subtraction 3 - 2

C language standard does not provide direct exponentiation operators like Python or JavaScript.
To calculate powers, you can:
・Use the pow() function (recommended)
・Implement it with a loop (for integer exponents)

Comparison Operators

Operator Description Usage
>/>= Greater than / Greater than or equal to 10 > 5 // true 10 >= 5 // true
<= Less than / Less than or equal to 10 < 5 # false 10 <= 11 // true
== Equal to
※Different from = (assignment operator)
10 == 10 // true
!= Not equal to 10 != 5 // true

Logical Operators

Returns a boolean type (True/False)

Operator Description Usage
&& Logical AND (10 > 5) && (5 < 10)
|| Logical OR (10 > 5) || (5 < 10)
! Negation !(10 > 5)

Functions

Functions are created with "returnDataType functionName(arguments) { // body of function }".
If there is no return value, `void` is specified.
※ They are sometimes called subroutines, or in object-oriented programming, procedures or methods.


// create function
bool isPositive(int num) {
  // return bool type data
  return num > 0;
}

// call created function int main() { int number = -5; if (isPositive(number)) { // Assuming std::cout is available, otherwise use printf printf("%d is positive. \n", number); } else { printf("%d isn't positive.\n", number); } return 0; }

Built-in functions available by using libraries

stdio.h

Function Name Functionality Example
printf() Outputs to the console

    #include <stdio.h>
    printf("hello, world!");
    
sizeof() Returns the size (in bytes) of the memory space occupied by a variable of the specified data type.
The return value of the function is strictly `size_t` (a positive integer type), but can be written as follows:

    #include <stdio.h>
    size_t charSize = sizeof(char);
    int intSize = sizeof(int);
    
fopen()
fclose()
Opens/closes the specified file.
Specified by `FILE *PointerVariable = fopen("filepath", "mode");`
mode: r - read, w - overwrite, a - append
Returns `NULL` if it cannot be opened.
Reasons for not being able to open:
・Specified file does not exist
・Insufficient file permissions
・File corruption
・Insufficient memory, etc.

    #include <stdio.h>
    FILE *file = fopen("cs50.txt", "w");
    if (file == NULL) // Cannot open the file
    {
        printf("Could not open file.\n");
        return 1;
    } else {
        fclose(file);
    }
    
sprintf() Generates a formatted string.
Writes the string to the specified buffer.
Basic usage:
`int sprintf(char *pointerVariable, "formatString", variableArgumentsList);`
`formatString` uses format specifiers (%i, %s, etc.) similar to `printf`.

    #include <stdio.h>
int main() {
    char buffer[50];
    int num = 123;
    float pi = 3.14;
    char str[] = "Hello";

    sprintf(buffer, "num = %d, pi = %.2f, str = %s", num, pi, str);
    printf("%s\n", buffer); // Output: num = 123, pi = 3.14, str = Hello

    return 0;
}
</code></pre></td>

stdlib.h

Function Name Functionality Example
atoi() Converts a string to an int.

    #include <stdlib.h>
    int intInput = atoi(s); // Corrected: string s is a C-style string (char*)
    
atof() Converts a string to a float.

    #include <stdlib.h>
    float floatInput = atof(s); // Corrected: string s is a C-style string (char*)
    
free() Frees dynamic memory.

    #include <stdlib.h>
    void free(*p); // p: pointer variable
    

ctype.h

Function Name Functionality Example
isalpha() Determines if a character is an alphabet.
Substitutes for regular expressions.

    #include <ctype.h>
    if (isalpha(text[i])) {
      // Process to execute when text[i] is an alphabet
    }
    
isdigit() Determines if a character is a digit.
The target for discrimination is **only one character**.

    #include <ctype.h>
    int input = isdigit(char c); // true or false (returns non-zero for true, 0 for false)
    
toupper() Converts a given lowercase alphabet character to uppercase.
Only 1 character can be converted.
If you want to convert a string, you need to loop.

    #include <ctype.h>
    // Convert only one character
    char c = 'a';
    char upper_c = toupper(c); // c → C
    // Convert a string
    char str[] = "Hello, World!";
    int i;
for (i = 0; str[i] != '\0'; i++) {
    printf("%c", toupper(str[i])); // HELLO WORLD
}
</code></pre></td>

math.h

Function Name Functionality Example
pow() Compute powers
The result is always returned of type double

    #include <math.h>
    double result1 = pow(2, 3); // the cube of 2, pow(base, exponent)
    // form a long shape
    long result2 = (long) round(pow(2, 3));
    

cs50.h

Function Name Functionality Example
get_string() Prompts the user to input a string.
To specify other types, change the function (e.g., `get_int()`).

      #include <stdio.h>
      #include <cs50.h>
  int main(void)
  {
    string answer = get_string("What's your name? ");
    printf("hello, %s\n", answer);
  }
  </code></pre></td>
strlen() Counts the number of characters in a `string` type.
The `string` type is available by using libraries.
Also, `string.h` needs to be imported.

    #include <cs50.h>
    #include <string.h>
    int numWord = strlen(s); // Corrected: s is a C-style string (char*)
    

string.h

Function Name Functionality Example
strcasecmp() Compares two strings case-insensitively.
Output is 0 when strings are equal.
Negative integer: s1 < s2
Positive integer: s1 > s2

      #include <stdio.h>
      #include <string.h>
  int main(void)
  {
      char str1[] = "Hello";
      char str2[] = "hello";
      char str3[] = "World";

      int result1 = strcasecmp(str1, str2);
      int result2 = strcasecmp(str1, str3);

      printf("result1: %d\n", result1); // Output: 0
      printf("result2: %d\n", result2); // Output: Negative integer
  }
  </code></pre></td>

Function Prototype Declaration

A function prototype declaration is
not always mandatory, but it informs the compiler about the function's information (return type, argument types, number of arguments).
The compiler checks if the function call is correct.

It is recommended to write them to improve code readability and maintainability.
By grouping prototype declarations at the beginning of the code, it becomes easier to grasp a list of functions used throughout the program, improving readability.
Also, if you change a function, you can maintain the consistency of the entire program by just changing the prototype declaration,
making maintenance of large-scale programs easier.

If a function is defined before it is called, the prototype declaration can be omitted.


#include <stdio.h>

// Prototype declaration int add(int a, int b);

int main(void) { int x = 5; int y = 3; int sum = add(x, y);

printf("Sum: %d\n", sum);

return 0;

}

// Function definition int add(int a, int b) { return a + b; }

Pointer Operators

In C language, a mechanism called pointers is provided to directly manipulate memory.
A pointer is a variable that stores a memory address, and it can be operated using the `*` and `&` operators.

`&` Operator: Address-of Operator

Used to get the memory address of a variable or function.


int x = 10;

// Initialize pointer p
int *p = NULL;
// Store the address of variable x in pointer p (a variable that stores a memory address)
p = &x; // Corrected: *p = &x would dereference p first

To avoid errors, **it is recommended to initialize pointer operators with `NULL` in C language**.
Reasons are as follows:

  1. **Danger of uninitialized pointers:**
    Just declaring a pointer variable does not determine the memory area it points to.
    An uninitialized pointer might point to a random location in memory (called a **dangling pointer**).
    Attempting to access memory through an uninitialized pointer can lead to:
    * **Illegal memory access**, potentially causing the operating system to forcefully terminate (crash) the program.
    * Overwriting unintended memory areas, which could corrupt data in the program or the entire system.
    * Malicious code exploiting uninitialized pointers, leading to security vulnerabilities.
  2. **Explicitly indicates that the pointer is not pointing to any memory area:**
    By initializing a pointer variable with `NULL`, you can perform `NULL` checks before using the pointer.
    This helps to avoid the problems of uninitialized pointers mentioned above.

`*` Operator: Dereference Operator

Used to access the memory area pointed to by a pointer.


int x = 10;
// Store the address of variable x in pointer p (a variable that stores a memory address)
int *p = &x;

printf("%d\n", *p); // 10

*p = 20; printf("%d\n", x); // 20

`->` (Arrow) Operator: Dereferencing function (accessing members from a pointer to a struct or union)


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Linked List Node structure struct Node { int data; struct Node *next; }; // Corrected: removed 'node' here

int main() { // Create the first node struct Node *head = (struct Node )malloc(sizeof(struct Node)); // Corrected: used struct Node and sizeof(struct Node) head->data = 1; head->next = NULL;

// Create the second node and add it to the list
struct Node *second = (struct Node *)malloc(sizeof(struct Node)); // Corrected
second->data = 2;
second->next = NULL;
head->next = second; // Create a link from the first node to the second node

// Create the third node and add it to the list
struct Node *third = (struct Node *)malloc(sizeof(struct Node)); // Corrected
third->data = 3;
third->next = NULL;
second->next = third; // Create a link from the second node to the third node

// Traverse the list and output data
struct Node *current = head;
while (current != NULL) {
    printf("%d\n", current->data);
    current = current->next; // Move to the next node
}

// Free memory (omitted in original, but important for dynamic memory)
free(head);
free(second);
free(third);

return 0;

}

WAV Operations

Creating a data array to store WAV data


// Declare an array 'header' to store 8-bit (unsigned/non-negative) integers (= allocate buffer)
uint8_t header[44]; // 44 elements
// Declare an array 'header' to store 16-bit (signed/positive and negative) integers (= allocate buffer)
int16_t header[44]; // 44 elements

File Operations (I/O Operations)

Files are a mechanism for storing data on storage devices such as hard disks.
In C language, the `FILE` structure is used to perform file operations.

When opening a file in C language, you need to manipulate a **file pointer (`FILE *`)** to operate on the file.
Pointers and file pointers are related but not exactly the same thing.
A file pointer is used to manage access to a file.

File Operation Basics

1. Opening a File

Use `fopen()` to open a file and get a file pointer.

2. Closing a File

Use `fclose()` to close a file and accept the file pointer.
Always close files when file operations are finished.
After `fclose`, I/O operations cannot be performed unless the file is opened again.

3. Managing File Pointer Access to the File

Use `fputs()` to manage the file pointer's access to the file (This is partially incorrect; `fputs` writes strings, not manages access. File pointer management is inherent to `fopen`, `fclose`, `fseek`, etc.).

4. Reading

Choose the appropriate function below depending on what you want to read:

  • `fgetc()`: Reads **one character at a time** from a file.
    ・Can be used for both text and binary files.
  • `fread()`: Reads a specified **number of bytes of data** from a file in bulk.
    ・Frequently used for reading binary files.
  • `fscanf()`: Reads **formatted data** from a file.
    ・Can read various types of data, such as strings, integers, and floating-point numbers.
    ・Can read data separated by whitespace characters (spaces, tabs, newlines).
    ・Care is needed when reading strings; specifying the number of characters to read is recommended to avoid buffer overflow.
     → For security considerations, it is recommended to use `fgets()` and `sscanf()` in combination.

When reading in a loop, `EOF` is useful.
`EOF` is a macro defined in the C standard library (`stdio.h`), usually indicating the end of a file with a value of -1.
In the case of input from a file, `EOF` is returned when the end of the file is reached.

5. Writing

Choose the appropriate function depending on the type, format, and purpose of the data you want to write.

  • `fputc()`: Writes **one character at a time** to a file.
    ・Can be used for both text and binary files.
  • `fputs()`: Writes a **string** to a file.
    ・Suitable for writing text files.
    ・Newline characters are not automatically added.
  • `fprintf()`: Writes **formatted data** to a file.
    ・Suitable for writing various formats of data to text files.
  • `fwrite()`: Writes a specified **number of bytes of data** to a file in bulk.
    ・Frequently used for writing binary files.
    ・Can write complex data structures such as structs in bulk.

#include <stdio.h>

int main() { FILE *fp; char c;

// Open the file in write mode
fp = fopen("test.txt", "w");
if (fp == NULL) {
    printf("Could not open file.\n");
    return 1;
}

// Write a string to the file
fputs("Hello, world!\n", fp);

// Close the file
fclose(fp);

// Open the file in read mode
fp = fopen("test.txt", "r");
if (fp == NULL) {
    printf("Could not open file.\n");
    return 1;
}

// Read and display characters from the file one by one
while ((c = fgetc(fp)) != EOF) {
    putchar(c);
}

// Close the file
fclose(fp);

return 0;

}


// Reading a file
#include <stdio.h>
fread(destinationMemoryAddress, numberOfBytesToRead, numberOfDataItemsToReadAtOnce, readHandleOpenedByFopen);

// Reading a file
#include <stdio.h>
fscanf(filePointer, "formatString", addressOfVariable, ...)

// Read integer and string separated by comma from file
fscanf(fp, "%d,%s", &num, str);

// Writing to a file
#include <stdio.h>
fwrite(sourceMemoryAddressToGetDataFrom, numberOfBytesToWrite, numberOfDataItemsToWriteAtOnce, writeHandleOpenedByFopen);

Other useful functions:
`fseek`: Moves the read/write position within a file.
`ftell`: Gets the current read/write position within a file.
`feof`: Determines if the end of the file has been reached.
`ferror`: Determines if an error occurred during file operation.

Header Files

Header files (`.h`) are special files used to organize program components and share information between multiple files.
By properly using header files, it is possible to efficiently develop even large-scale programs.

Roles of header files:
・**Function prototype declarations:**
 Inform the compiler what arguments a function accepts and what type of value it returns.
 This allows that function to be called from other files before it is defined.
・**Structure and union definitions:**
 Define complex data structures that can be shared across multiple files.
 This helps maintain data consistency and improves code reusability.
・**Macro definitions:**
 Assign names to constants or short code snippets, improving code readability and maintainability.
 For example, pi can be defined as a macro named `PI`.
・**Type definitions:**
 Define type aliases using `typedef`, etc.
・**External variable declarations:**
 By using the `extern` keyword, variables defined in one file can be used in other files.

Advantages of Header Files
・Code Organization: Grouping related declarations and definitions in header files organizes code and improves readability.
・Code Reusability: By collecting common declarations and definitions in header files, they can be reused across multiple files.
・Reduced Compilation Time: Properly segmenting header files means only modified files need recompilation, reducing overall compilation time.
・Improved Maintainability: Centralizing definitions in header files limits the scope of changes when modifications are needed, improving maintainability.

Example of a Header File


// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// Function prototype declaration int add(int a, int b);

// Struct definition typedef struct { int x; int y; } Point;

// Macro definition #define PI 3.14159

#endif

To include a header file in a `.c` file, use the `#include` preprocessor directive.


// myprogram.c
#include "myheader.h"
#include 

int main() { // Write processing return 0; }

Include Guards

In C language, the same header file (.h file) may be included in multiple files.
In such cases, without include guards, the content of the same header file would be read multiple times, leading to compilation errors.

Include guards are a mechanism to prevent a header file from being included multiple times.
This helps avoid compilation errors and ensures the program runs correctly.

Mechanism of Include Guards
Include guards are implemented using a compiler feature called the preprocessor.
The preprocessor processes the source code before actual compilation, replacing or deleting code based on specific conditions.
Include guards use a combination of the following three preprocessor directives:

`#ifndef`: Activates the subsequent code if the specified macro is not defined.
`#define`: Defines the specified macro.
`#endif`: Terminates the conditional compilation block started by `#ifndef`.

Example of Include Guard Implementation
An example of implementing an include guard in a header file named myheader.h.


// Checks if the macro MYHEADER_H is defined using #ifndef.
// On subsequent inclusions, since MYHEADER_H is already defined, the #ifndef condition becomes false, and the header file content is ignored.
#ifndef MYHEADER_H
// If MYHEADER_H is not defined, #define MYHEADER_H defines MYHEADER_H and activates the header file content.
#define MYHEADER_H

// Header file content (function prototype declarations, struct definitions, etc.)

#endif

Naming Convention for Include Guards
The name of the macro used for an include guard must be unique and based on the header file name.
Typically, the header file name is converted to uppercase, and "_H" is appended to it.

Preprocessor

A preprocessor is a program that preprocesses source code before the compiler performs the actual compilation.
The preprocessor interprets specific instructions (preprocessor directives) within the source code and performs operations such as transforming the source code or including other files.

Preprocessor Directives
Preprocessor directives are instructions that begin with the `#` symbol and are processed by the preprocessor, not the compiler.
Preprocessor directives are used for various purposes, including text substitution, conditional compilation, and file inclusion.

  • `#define`
    Used to define macros that replace a specific string with another string.
    Helpful for defining constants and improving code readability.
    Example: `#define PI 3.14` = Replaces the string PI with 3.14.

Structs

A feature that defines a new, unique data type by combining basic data type variables into a single unit.
Uses `struct`.
By using structs, you can group related data, organize code, and represent complex data structures.

Reference

Struct Definition

Describe the struct definition in a ".h" header file.
→ This avoids code duplication and enhances reusability.
Furthermore, if the struct definition needs to be changed, only the .h file needs to be modified, and the changes will be reflected in all .c files that include it.
→ This improves maintainability and prevents missed corrections.

"`struct StructName`" becomes the new data type name.


struct StructName {
    TypeName Member1;
    TypeName Member2;
    ...
};

By using the `typedef` feature, you can assign a new, shorter name to an existing data type or a data type you've created.
= It's like a "shorthand" to enable using a long data type name with a shorter name.
This makes the code shorter and simpler, improving readability.


typedef struct StructName{ // StructName is optional
    TypeName Member1;
    TypeName Member2;
    ...
} StructAbbreviation;

Struct Variable Declaration

By including (reading) the .h (header file), the same struct can be used in multiple .c files.
Basically the same as a normal variable declaration: `DataType VariableName`


struct StructName variableName;

// If typedef is used (assigning a struct abbreviation) StructAbbreviation variableName;

Accessing Struct Members


variableName.memberName

Implementation of Pointed List

By using structs, flexible data types can be realized, enabling the implementation of Pointed List data structures.

Singly Linked List


struct node {
    int data;          // Data (e.g., integer)
    struct node *next; // Pointer to the next node
} node;

#include <stdio.h>
#include <stdlib.h>

// Function to add a node to the beginning of the list node *addNode(node *head, int data) { node *newNode = (node *)malloc(sizeof(node)); if (newNode == NULL) { printf("Memory allocation error\n"); return head; } newNode->data = data; newNode->next = head; return newNode; }

// Function to display the elements of the list void displayList(node *head) { node *current = head; while (current != NULL) { printf("%d -> ", current->data); current = current->next; } printf("NULL\n"); }

int main() { node *head = NULL; // Create an empty list

head = addNode(head, 3);
head = addNode(head, 7);
head = addNode(head, 1);

displayList(head); // Display the list

// Free memory
node *current = head;
while (current != NULL) {
    node *temp = current;
    current = current->next;
    free(temp);
}
head = NULL; // Set the head pointer of the list to NULL

return 0;

}

Doubly Linked List


#include <stdio.h>
#include <stdlib.h>

typedef struct Node { int data; struct Node *prev; // Pointer to the previous node struct Node *next; // Pointer to the next node } node;


// Function to create a new node
Node *createNode(int data) {
    node *newNode = (node *)malloc(sizeof(node));
    if (newNode == NULL) {
        printf("Memory allocation error\n");
        return NULL;
    }
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// Function to insert a node at the beginning of the list
node *insertAtBeginning(node *head, int data) {
    node *newNode = createNode(data);
    if (newNode == NULL) {
        return head;
    }
    if (head == NULL) {
        return newNode;
    }
    newNode->next = head;
    head->prev = newNode;
    return newNode;
}

// Function to insert a node at the end of the list
node *insertAtEnd(node *head, int data) {
    node *newNode = createNode(data);
    if (newNode == NULL) {
        return head;
    }
    if (head == NULL) {
        return newNode;
    }
    node *current = head;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = newNode;
    newNode->prev = current;
    return head;
}

// Function to delete a node from the list
node *deleteNode(node *head, node *target) {
    if (head == NULL || target == NULL) {
        return head;
    }
    if (target->prev != NULL) {
        target->prev->next = target->next;
    } else {
        head = target->next;
    }
    if (target->next != NULL) {
        target->next->prev = target->prev;
    }
    free(target);
    return head;
}

// Function to display the elements of the list
void displayList(node *head) {
    node *current = head;
    while (current != NULL) {
        printf("%d <-> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

// Function to free memory of the list
void freeList(node *head) {
    node *current = head;
    while (current != NULL) {
        node *temp = current;
        current = current->next;
        free(temp);
    }
}

Stacks


#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 10 // Maximum size of the stack

typedef struct { int data[MAX_SIZE]; int top; // Index indicating the top of the stack } Stack;

// Function to initialize the stack void initialize(Stack *stack) { stack->top = -1; }

// Function to check if the stack is empty int isEmpty(Stack *stack) { return stack->top == -1; }

// Function to check if the stack is full int isFull(Stack *stack) { return stack->top == MAX_SIZE - 1; }

// Function to push an element onto the stack void push(Stack *stack, int value) { if (isFull(stack)) { printf("Stack is full.\n"); return; } stack->data[++stack->top] = value; }

// Function to pop an element from the stack int pop(Stack *stack) { if (isEmpty(stack)) { printf("Stack is empty.\n"); return -1; // Return an error value } return stack->data[stack->top--]; }

int main() { Stack stack; initialize(&stack);

push(&stack, 1);
push(&stack, 2);
push(&stack, 3);

printf("Popped element: %d\n", pop(&stack));
printf("Popped element: %d\n", pop(&stack));

return 0;

}

Queues


#include <stdio.h>
#include <stdlib.h>

typedef struct Node { int data; struct Node *next; } Node;

typedef struct { Node *front; // Pointer indicating the front of the queue Node *rear; // Pointer indicating the rear of the queue int val; } Queue;

// Function to initialize the queue void initializeQueue(Queue *queue) { queue->front = NULL; queue->rear = NULL; }

// Function to check if the queue is empty int isEmptyQueue(Queue *queue) { return queue->front == NULL; }

// Function to enqueue an element into the queue void enqueue(Queue *queue, int data) { Node *newNode = (Node *)malloc(sizeof(Node)); if (newNode == NULL) { printf("Memory allocation error\n"); return; } newNode->data = data; newNode->next = NULL;

if (isEmptyQueue(queue)) {
    queue->front = newNode;
    queue->rear = newNode;
} else {
    queue->rear->next = newNode;
    queue->rear = newNode;
}

}

// Function to dequeue an element from the queue int dequeue(Queue *queue) { if (isEmptyQueue(queue)) { printf("Queue is empty\n"); return -1; // Return an error value }

Node *temp = queue->front;
int data = temp->data;

queue->front = queue->front->next;
if (queue->front == NULL) {
    queue->rear = NULL;
}

free(temp);
return data;

}

// Function to display elements of the queue void displayQueue(Queue *queue) { Node *current = queue->front; if (isEmptyQueue(queue)) { printf("Queue is empty\n"); return; } printf("Queue elements: "); while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n"); }

int main() { Queue queue; initializeQueue(&queue);

enqueue(&queue, 1);
enqueue(&queue, 2);
enqueue(&queue, 3);

displayQueue(&queue);

printf("Dequeued element: %d\n", dequeue(&queue));
displayQueue(&queue);

printf("Dequeued element: %d\n", dequeue(&queue));
displayQueue(&queue);

printf("Dequeued element: %d\n", dequeue(&queue));
displayQueue(&queue);

printf("Dequeued element: %d\n", dequeue(&queue));

return 0;

}

Hash Table


// Struct definition
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 10 // Size of the hash table

typedef struct Node { char *key; int value; struct Node *next; } Node;

typedef struct { Node *table[TABLE_SIZE]; } HashTable;

// Hash function implementation unsigned int hash(char *key) { unsigned int hash = 0; for (int i = 0; key[i] != '\0'; i++) { hash = 31 * hash + key[i]; } return hash % TABLE_SIZE; }

// Function to insert an element into the hash table void insert(HashTable *ht, char *key, int value) { unsigned int index = hash(key); Node *newNode = (Node *)malloc(sizeof(Node)); if (newNode == NULL) { printf("Memory allocation error\n"); return; } newNode->key = strdup(key); // Save a copy of the key newNode->value = value; newNode->next = ht->table[index]; ht->table[index] = newNode; }

// Function to search for an element in the hash table Node *search(HashTable *ht, char *key) { unsigned int index = hash(key); Node *current = ht->table[index]; while (current != NULL) { if (strcmp(current->key, key) == 0) { return current; } current = current->next; } return NULL; // Not found }

// Function to delete an element from the hash table void delete(HashTable *ht, char *key) { unsigned int index = hash(key); Node *current = ht->table[index]; Node *prev = NULL; while (current != NULL) { if (strcmp(current->key, key) == 0) { if (prev == NULL) { ht->table[index] = current->next; } else { prev->next = current->next; } free(current->key); // Free key memory free(current); return; } prev = current; current = current->next; } }

int main() { HashTable ht; // Assuming initHashTable is defined elsewhere to initialize ht.table to NULLs // For this example, let's just manually initialize the first few elements for demonstration. for (int i = 0; i < TABLE_SIZE; i++) { ht.table[i] = NULL; }

insert(&ht, "apple", 1);
insert(&ht, "banana", 2);
insert(&ht, "orange", 3);

Node *result = search(&ht, "banana");
if (result != NULL) {
    printf("Key: %s, Value: %d\n", result->key, result->value);
} else {
    printf("Key not found\n");
}

delete(&ht, "banana");

result = search(&ht, "banana");
if (result == NULL) {
    printf("Key: banana has been deleted\n");
}

return 0;

}

Trie


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define ALPHABET_SIZE 26 // Number of characters in the alphabet

typedef struct TrieNode { struct TrieNode *children[ALPHABET_SIZE]; bool isEndOfWord; // Whether it's the end of a word } TrieNode;

// Function to create a new Trie node TrieNode *createNode() { TrieNode *newNode = (TrieNode *)malloc(sizeof(TrieNode)); if (newNode == NULL) { printf("Memory allocation error\n"); return NULL; } newNode->isEndOfWord = false; for (int i = 0; i < ALPHABET_SIZE; i++) { newNode->children[i] = NULL; } return newNode; }

// Convert character to index int charToIndex(char c) { return c - 'a'; }

// Function to insert a word into the Trie void insert(TrieNode *root, char *word) { TrieNode *current = root; for (int i = 0; word[i] != '\0'; i++) { int index = charToIndex(word[i]); if (current->children[index] == NULL) { current->children[index] = createNode(); } current = current->children[index]; } current->isEndOfWord = true; }

// Function to search for a word in the Trie bool search(TrieNode *root, char *word) { TrieNode *current = root; for (int i = 0; word[i] != '\0'; i++) { int index = charToIndex(word[i]); if (current->children[index] == NULL) { return false; } current = current->children[index]; } return current->isEndOfWord; }

int main() { TrieNode *root = createNode();

insert(root, "apple");
insert(root, "banana");
insert(root, "orange");

printf("apple: %s\n", search(root, "apple") ? "Found" : "Not found");
printf("grape: %s\n", search(root, "grape") ? "Found" : "Not found");

return 0;

}

Bitmap (BMP) Processing

Struct data types used in the Bitmap (BMP) file format.
In C language, these structs can be directly defined and read from/written to files.
The BMP file format is widely used in Windows for images, and these header structs are also based on Windows specifications.

Struct Data Types

Struct Data Type Description Information Held Number of Bytes
BITMAPFILEHEADER Struct to store file header information of a bitmap file. File type, size, starting position of image data, offset, etc. 14
BITMAPINFOHEADER Struct to store image header information of a bitmap file. Image width, height, color depth, compression method, etc. 40
⚠️ **GitHub.com Fallback** ⚠️