Key concepts in C/C++
Static Functions
A static function is a function whose scope is limited to the file or class in which it is declared. It has special properties depending on whether it's used in C or C++ (or other programming languages), but its key feature is its limited accessibility.
1. Static Functions in C
Definition:
A static function in C is a function that is only visible within the file in which it is declared. It is declared using the static
keyword.
Purpose:
Restrict the scope of the function to the file.
Prevent name conflicts with functions of the same name in other files.
#include <stdio.h>
static void printMessage() {
printf("This is a static function.\n");
}
void callStaticFunction() {
printMessage();
}
Explanation:
The function
printMessage
can only be called within the file where it is declared.If another file defines a function with the same name, there will be no conflict.
Static Functions in C++
Definition:
In C++, a static function is a member function of a class that:
Does not operate on an instance of the class.
Belongs to the class itself rather than any specific object of the class.
Key Properties:
Can be called using the class name.
Cannot access non-static members or
this
pointer directly.Useful for utility functions related to a class.
Example:
#include <iostream>
class MyClass {
public:
static int count;
static void printCount() {
std::cout << "Count: " << count << std::endl;
}
};
int MyClass::count = 10;
int main() {
MyClass::printCount(); // Call without creating an object
return 0;
}
Explanation:
printCount
is a static function and can be called without an object ofMyClass
.It accesses only static data members, such as
count
.
Advantages of Static Functions
Encapsulation:
Limits the scope of the function in C.
Encapsulates utility functions in a class in C++.
Avoid Name Conflicts:
Prevents global namespace pollution in C.
Functions with the same name in different classes are distinct in C++.
Efficient Utility Functions:
- Provides a way to write helper functions related to a class in C++ without needing an instance.
Real-World Use Cases
In C:
- Modular Programming: Keep helper functions private to a file.
In C++:
Singleton Pattern: Use static functions for managing single-instance classes.
Global Counters: Use static functions to maintain and provide access to global counters within a class.
volatile
Keyword in C
The volatile
keyword in C is used to tell the compiler that a variable's value can change at any time, outside the program's control. This prevents the compiler from optimizing the code in ways that assume the variable's value does not change unexpectedly.
Why Use volatile
?
In certain cases, a variable's value may change due to external factors, such as:
Hardware: Reading from hardware registers or memory-mapped I/O.
Interrupts: Variables modified by an interrupt service routine (ISR).
Multithreading: Shared variables accessed by multiple threads.
Without volatile
, the compiler might optimize the code in ways that cause incorrect behavior, such as:
Storing the variable's value in a register and not checking the actual memory.
Reordering reads and writes to the variable.
Example Without volatile
Code:
#include <stdio.h>
int flag = 0;
// fix : volatile int flag = 0;
void someFunction() {
while (flag == 0) {
// Do nothing, waiting for flag to change
}
printf("Flag changed!\n");
}
Issue:
- The compiler might optimize the
while (flag == 0)
loop by assumingflag
will never change since it isn't modified within the code. This can result in an infinite loop, even ifflag
is updated by an external event, such as an interrupt.
Fix:
By declaring
flag
asvolatile
, the compiler is forced to:Read
flag
directly from memory each time.Avoid optimizations that assume
flag
does not change.
Common Use Cases
a. Memory-Mapped I/O
#define STATUS_REGISTER (*(volatile unsigned int *)0x40004000)
void checkStatus() {
while (!(STATUS_REGISTER & 0x01)) {
// Wait until bit 0 of the status register is set
}
}
b. Interrupt Service Routines (ISRs)
volatile int dataReady = 0;
void ISR() {
dataReady = 1; // Set by interrupt
}
void mainFunction() {
while (!dataReady) {
// Wait for interrupt
}
printf("Data is ready!\n");
}
volatile
ensures the main function doesn't cache the value of dataReady
and always checks its actual value.
Endianness
Endianness refers to the order in which bytes are arranged in memory for multi-byte data types like integers, floats, etc. It determines whether the most significant byte (MSB) or the least significant byte (LSB) is stored at the lowest memory address.
Types of Endianness
1. Little-Endian
The least significant byte (LSB) is stored first (at the lowest memory address).
Example: For the hexadecimal value
0x12345678
(32-bit integer),- Memory representation:
78 56 34 12
- Memory representation:
2. Big-Endian
The most significant byte (MSB) is stored first (at the lowest memory address).
Example: For the same value
0x12345678
,- Memory representation:
12 34 56 78
- Memory representation:
Illustration
Assume we have a 32-bit integer 0x12345678
stored in memory at address 0x1000
. Here's how it would look in both systems:
Address | Little-Endian | Big-Endian |
0x1000 | 78 | 12 |
0x1001 | 56 | 34 |
0x1002 | 34 | 56 |
0x1003 | 12 | 78 |
Example
Little-Endian
Processor reads
0x78
(from the lowest address0x1000
) as the least significant byte.The final value is reconstructed as
0x12345678
.
Big-Endian
Processor reads
0x12
(from the lowest address0x1000
) as the most significant byte.The final value is reconstructed as
0x12345678
.
Code Example
To detect endianness, as discussed earlier:
#include <stdio.h>
void checkEndianness() {
unsigned int num = 1; // Step 1: Declare an unsigned integer with value 1.
char *ptr = (char *)# // Step 2: Point to the memory address of 'num' using a char pointer.
if (*ptr == 1) { // Step 3: Check the value stored at the first byte of 'num'.
printf("System is Little Endian\n");
} else {
printf("System is Big Endian\n");
}
}
int main() {
checkEndianness(); // Call the function to check endianness.
return 0;
}
How the Code Works
Step 1: Declare an Integer
unsigned int num = 1;
num
is a 4-byte (32-bit) unsigned integer with the value1
.In memory,
num
will be stored in binary as: 00000000 00000000 00000000 00000001
Step 2: Use a Character Pointer
char *ptr = (char *)#
The
&num
operator retrieves the address of the variablenum
.The pointer
ptr
is declared as a char pointer, meaning it will interpret the memory ofnum
one byte at a time (sincechar
is 1 byte in size).
Step 3: Check the Value of the First Byte
if (*ptr == 1)
*ptr
dereferences the pointer to access the first byte ofnum
.
Depending on the system's endianness:
Little-Endian:
The least significant byte (LSB) is stored at the lowest memory address.
Memory representation of
num
(value = 1):Address | Byte --------|----- 0x1000 | 01 0x1001 | 00 0x1002 | 00 0x1003 | 00
In this case,
*ptr
will read01
, so the program prints: System is Little Endian
Big-Endian:
The most significant byte (MSB) is stored at the lowest memory address.
Memory representation of
num
(value = 1):Address | Byte --------|----- 0x1000 | 00 0x1001 | 00 0x1002 | 00 0x1003 | 01
Structures
A structure in C is a user-defined data type that allows grouping variables of different types under one name.
Example:
#include <stdio.h>
struct Employee {
int id; // Integer field
char name[50]; // String (array of characters)
float salary; // Floating-point field
};
int main() {
struct Employee emp1; // Declare a structure variable
// Assign values
emp1.id = 101;
emp1.salary = 50000.5;
snprintf(emp1.name, 50, "Alice"); // Safely copy string
// Print values
printf("ID: %d, Name: %s, Salary: %.2f\n", emp1.id, emp1.name, emp1.salary);
return 0;
}
Structures may include padding to align members in memory, improving performance.
Useful for managing memory by storing flags or small integers in a structure.
struct Flags {
unsigned int isRunning : 1; // 1-bit field
unsigned int hasError : 1; // 1-bit field
unsigned int priority : 3; // 3-bit field (0-7)
};
Unions
A union is similar to a structure, but all its members share the same memory location. This means a union can only store one member value at a time.
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 42;
printf("Integer: %d\n", data.i);
data.f = 3.14;
printf("Float: %.2f\n", data.f);
snprintf(data.str, 20, "Hello");
printf("String: %s\n", data.str);
return 0;
}
Only the largest member size determines the memory allocated.
Efficient use of memory when only one field is needed at a time (e.g., interpreting data types).
Unions can interpret data as multiple types.
Enumerations (Enums)
An enum is a user-defined data type that consists of integral constants.
#include <stdio.h>
enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
int main() {
enum Weekday today = WED;
if (today == WED) {
printf("It's Wednesday!\n");
}
return 0;
}
Constants start from 0
by default (MON = 0, TUE = 1, etc.).