- Keywords
1.1 Data Type Keywords
The fundamental data type keywords in C include:
char, short, int, long, float, double
struct, union, enum, signed, unsigned, void
On a 32-bit platform, the following table shows the memory allocation for each type:
| Type | Description | Size in Bytes |
|---|---|---|
| char | Character type | 1 byte |
| short | Short integer | 2 bytes |
| int | Integer | 4 bytes |
| long | Long integer | 4 bytes |
| float | Single-precision floating point | 4 bytes |
| double | Double-precision floating point | 8 bytes |
| struct | Structure | variable |
| union | Union | variable |
| enum | Enumeration | variable |
| signed | Signed number | variable |
| unsigned | Unsigned number | variable |
1.2 Storage Class Keywords
These keywords control how variables are stored in memory:
register, static, const, auto, extern
| Keyword | Purpose |
|---|---|
| register | Request register storage |
| static | Static storage duration |
| const | Read-only variable |
| auto | Automatic storage |
| extern | External linkage |
1.2.1 The const Qualifier
The const qualifier marks a variable as read-only. The variable still exists in memory but cannot be modified after initialization.
#include <stdio.h>
int main(int argc, char **argv)
{
const int value = 99;
value = 100; // Compilation error: assignment to read-only variable
return 0;
}
Attempting to compile this code produces the following error:
$ gcc example.c
example.c:5:7: error: assignment of read-only variable 'value'
1.2.2 The register Storage Class
The register keyword suggests that the variable be stored in a CPU register for faster access. Note that you cannot obtain the address of a register variable using the address-of operator.
#include <stdio.h>
int main(int argc, char **argv)
{
register int counter = 0;
&counter; // Error: address-of operator cannot be applied to register variable
return 0;
}
Compilation result:
$ gcc example.c
example.c:5:5: error: address of register variable 'counter' requested
1.3 Control Flow Keywords
if, else, break, continue, for, while, do, switch, case, goto, default
1.4 Other Keywords
sizeof, typedef, volatile
sizeof: Returns the size of a type in bytestypedef: Creates an alias for an existing typevolatile: Prevents compiler optimization
1.4.1 The volatile Qualifier
The volatile keyword serves two purposes:
- Forces the compiler to read the value from memory each time (not use cached value)
- Prevents the compiler from optimizing away what it considers "dead code"
1.4.2 Using sizeof
#include <stdio.h>
int main(int argc, char **argv)
{
int number = 0;
printf("%lu ", sizeof(number));
printf("%lu ", sizeof(int));
char letter = 'A';
printf("%lu ", sizeof(char));
printf("%lu ", sizeof(letter));
printf("%lu\n", sizeof('A')); // Treated as integer in C
return 0;
}
Output:
$ ./a.out
4 4 1 1 4
1.4.3 Using typedef
The typedef keyword creates an alias for an existing type without creating a new type. This is useful for shortening complex type definitions.
Process for creating a typedef:
- Define a variable using the original type
- Replace the variable name with the desired alias
- Add
typedefat the beginning
Example:
Step 1: int device_id;
Step 2: int ID;
Step 3: typedef int ID;
ID device_id; // Equivalent to int device_id;
- Data Types
2.1 Variables
Naming Rules: Variable names must start with a letter or underscore, cannot be a keyword, and can contain only letters, digits, and underscores.
int auto; // Error: cannot use keyword as variable name
int 3value; // Error: cannot start with digit
Always initialize variables when declaring them to avoid undefined behavior.
int status = ACTIVE; // Initialization
int result;
result = READY; // Assignment (not initialization)
Declaration, Definition, and Usage
- Definition: Alllocates storage space for the variable
- Declaration: Announces the variable's type and name
- Usage: Reading from or writing to the variable
Reading Input with scanf
int scanf(const char *format, ...);
format: Format string specifying input pattern
Example 1: Basic input
#include <stdio.h>
int main(int argc, char **argv)
{
int value = 0;
printf("Enter an integer: ");
scanf("%d", &value);
printf("value = %d\n", value);
return 0;
}
Output:
$ ./a.out
Enter an integer: 42
value = 42
Important: The %d format specifier stops scanning when it encounters a non-numeric character. However, the invalid characters remain in the input buffer and must be cleared.
Example 2: Input with delimiter
#include <stdio.h>
int main(int argc, char **argv)
{
int first = 0;
int second = 0;
printf("Enter two numbers (format: num1:num2): ");
scanf("%d:%d", &first, &second);
printf("%d + %d = %d\n", first, second, first + second);
return 0;
}
Output:
$ ./a.out
Enter two numbers (format: num1:num2): 15:25
15 + 25 = 40
2.2 Character Type
Character data is stored internally as ASCII values (numeric codes).
#include <stdio.h>
int main(int argc, char **argv)
{
printf("%c\n", 'M');
printf("%d\n", 'M');
return 0;
}
Output:
$ ./a.out
M
77
Single Quotes:
- Single quotes surrounding a character indicate it should be treated as a character literal
- The value inside single quotes represents the ASCII code of that character
Character Initialization
char ch;
char ch = 'Q';
char ch = '\0'; // Null character
char ch = 0; // Numeric zero (same as '\0')
char ch = '0'; // Character '0', ASCII value 48
Example 1: Reading a character
#include <stdio.h>
int main(int argc, char **argv)
{
char ch = '\0';
printf("Enter a character: ");
ch = getchar();
getchar(); // Consume the newline character from Enter key
printf("%c\n", ch);
printf("%d\n", ch);
return 0;
}
Output:
$ ./a.out
Enter a character: k
k
107
Example 2: Case conversion
This example converts uppercase letters to lowercase and vice versa:
#include <stdio.h>
int main(int argc, char **argv)
{
char ch = '\0';
printf("Enter a character: ");
ch = getchar();
getchar();
if (ch >= 'a' && ch <= 'z')
{
ch -= 32; // Convert to uppercase
}
else if (ch >= 'A' && ch <= 'Z')
{
ch += 32; // Convert to lowercase
}
else
{
printf("Invalid input\n");
return -1;
}
printf("%c\n", ch);
return 0;
}
Output:
$ ./a.out
Enter a character: G
g
2.3 Floating-Point Types
Floating-point literals ending with f are of type float. Those without the suffix are of type double.
#include <stdio.h>
int main(int argc, char **argv)
{
float f = 0.0f;
double d = 0.0;
printf("Enter two floating-point numbers: ");
scanf("%f %lf", &f, &d);
printf("%f %lf\n", f, d);
return 0;
}
Output:
$ ./a.out
Enter two floating-point numbers: 2.5 7.8
2.500000 7.800000
2.4 Signed and Unsigned Types
| Type | Format Specifier |
|---|---|
| int | %d |
| short | %hd |
| long | %ld |
| unsigned int | %u |
| unsigned short | %hu |
| unsigned long | %lu |
- Number Systems
C language does not directly support binary output, but can output decimal, octal, and hexadecimal values.
| Base | Format Specifier |
|---|---|
| Decimal | %d |
| Octal | %o or %#o |
| Hexadecimal | %x or %#x |
Example: Number system conversion
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned int data = 137;
printf("%d\n", data); // Decimal output
printf("%o ", data); // Octal output
printf("%#o\n", data); // Octal with prefix
printf("%x ", data); // Hex output
printf("%#x\n", data); // Hex with prefix
return 0;
}
Output:
$ ./a.out
137
211 0211
89 0x89
- Sign Representation: Sign-Magnitude, One's Complement, and Two's Complement
- Computers store signed integers using two's complement representation
- One's complement serves as an intermediate step between sign-magnitude and two's complement
- Sign-magnitude represents the binary form of the magnitude bits
Why Two's Complement?
-
Unique zero representation:``` +0: 0000 0000 -0: 0000 0000
-
Simplifies arithmetic: Subtraction can be performed using addition
-
Integer Output Interpretation
When using %d format specifier with either int or unsigned int storage, the compiler examines the most significant bit (bit 31) of the 4-byte value:
- If bit 31 is 1: The value is interpreted as a negative two's complement number
- If bit 31 is 0: The value is interpreted as a positive sign-magnitude number
#include <stdio.h>
int main(int argc, char **argv)
{
int a = 0x80000005;
unsigned int b = 0x80000005;
int c = 0x00000015;
unsigned int d = 0x00000015;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
return 0;
}
Output:
$ ./a.out
a = -2147483643
b = -2147483643
c = 21
d = 21
For char type (1 byte), the interpreter examines bit 7:
- If bit 7 is 1: Output is negative two's complement
- If bit 7 is 0: Output is positive sign-magnitude
For unsigned char, the value is always interpreted as a positive sign-magnitude number.
#include <stdio.h>
int main(int argc, char **argv)
{
char c1 = 0xf6;
char c2 = 0x06;
unsigned char c3 = 0xf6;
unsigned char c4 = 0x06;
printf("c1 = %d\n", c1);
printf("c2 = %d\n", c2);
printf("c3 = %d\n", c3);
printf("c4 = %d\n", c4);
return 0;
}
Output:
$ c1 = -10
c2 = 6
c3 = 246
c4 = 6
Using %#x format specifier: For both signed and unsigned int types, the raw bit pattern is displayed. For char, if the high bit is set, the value is sign-extended to 4 bytes.
#include <stdio.h>
int main(int argc, char **argv)
{
int n1 = 0xff000009;
int n2 = 0x00000019;
unsigned int n3 = 0xff000009;
unsigned int n4 = 0x00000019;
char c1 = 0xf8;
char c2 = 0x18;
unsigned char c3 = 0xf8;
unsigned char c4 = 0x18;
printf("n1 = %#x\n", n1);
printf("n2 = %#x\n", n2);
printf("n3 = %#x\n", n3);
printf("n4 = %#x\n", n4);
printf("c1 = %#x\n", c1);
printf("c2 = %#x\n", c2);
printf("c3 = %#x\n", c3);
printf("c4 = %#x\n", c4);
return 0;
}
Output:
$ ./a.out
n1 = 0xff000009
n2 = 0x19
n3 = 0xff000009
n4 = 0x19
c1 = 0xfffffff8
c2 = 0x18
c3 = 0xf8
c4 = 0x18
Using %u format specifier: Always displays the unsigned enterpretation of the bit pattern.
#include <stdio.h>
int main(int argc, char **argv)
{
int val1 = 0x80000007;
int val2 = 0x00000017;
unsigned int val3 = 0x80000007;
unsigned int val4 = 0x00000017;
char ch1 = 0xf2;
char ch2 = 0x12;
unsigned char ch3 = 0xf2;
unsigned char ch4 = 0x12;
printf("val1 = %u\n", val1);
printf("val2 = %u\n", val2);
printf("val3 = %u\n", val3);
printf("val4 = %u\n", val4);
printf("ch1 = %u\n", ch1);
printf("ch2 = %u\n", ch2);
printf("ch3 = %u\n", ch3);
printf("ch4 = %u\n", ch4);
return 0;
}
Output:
$ ./a.out
val1 = 2147483655
val2 = 23
val3 = 2147483655
val4 = 23
ch1 = 4294967282
ch2 = 18
ch3 = 242
ch4 = 18
- Escape Sequences
Escape sequences combine the backslash with certain characters to produce special meanings.
6.1 Octal Escape Sequences
Format: '\ddd'
- Each digit d must be in range 0-7
- Maximum of 3 octal digits are recognized
Valid examples:
'\101' // Valid (octal 101 = decimal 65 = 'A')
'\377' // Maximum valid (octal 377 = decimal 255)
Invalid examples:
'\278' // Invalid: digit 8 is not in octal range
'\400' // Invalid: exceeds maximum value 255
Important: Octal escape sequences have an overflow consideration. The maximum valid value is 255 (\377 in octal).
char c = '\0';
for (; c < 200; c++); // Potential infinite loop due to overflow
6.2 Hexadecimal Escape Sequences
Format: '\xhh'
- Each digit h must be in range 0-9, a-f, or A-F
- Maximum of 2 hexadecimal digits are recognized
- Type Conversions
7.1 Implicit Type Conversion
Example 1: int combined with unsigned int promotes to unsigned int
#include <stdio.h>
int main(int argc, char **argv)
{
int a = -20;
unsigned int b = 8;
if (a + b > 0) // Comparison uses unsigned interpretation
{
printf("Greater than 0\n");
}
else
{
printf("Less than or equal to 0\n");
}
printf("%d\n", a + b); // %d sees sign bit set, outputs negative
printf("%u\n", a + b); // %u shows raw unsigned value
return 0;
}
Output:
$ ./a.out
Greater than 0
-12
4294967284
Example 2: int mixed with double promotes to double
#include <stdio.h>
int main(int argc, char **argv)
{
int x = 5;
double y = 3.14;
printf("%lu\n", sizeof(x + y)); // sizeof(double) = 8 bytes
return 0;
}
Output:
$ ./a.out
8
Example 3: char and short types are automatically promoted to int when used in expressions
#include <stdio.h>
int main(int argc, char **argv)
{
short s = 10;
char c = 'X';
printf("%lu\n", sizeof(s + s)); // 4 (promoted to int)
printf("%lu\n", sizeof(s + c)); // 4 (promoted to int)
printf("%lu\n", sizeof(c + c)); // 4 (promoted to int)
return 0;
}
Output:
4
4
4
7.2 Explicit Type Conversion (Casting)
(type_name)(expression)
(int)ptr + offset // Cast then add
(int)(ptr + offset) // Add then cast
- Operators
Operators are classified by the number of operands they require:
- Unary operators: Require one operand
- Binary operators: Require two operands
- Ternary operators: Require three operands
- N-ary operators: Require multiple operands
8.1 Arithmetic Operators
| Operation | Symbol |
|---|---|
| Addition | + |
| Subtraction | - |
| Multiplication | * |
| Division | / |
| Modulo | % |
| Add and assign | += |
| Subtract and assign | -= |
| Multiply and assign | *= |
| Divide and assign | /= |
| Modulo and assign | %= |
#include <stdio.h>
int main(int argc, char **argv)
{
printf("%d\n", 17 / 5); // Integer division
printf("%lf\n", 17 / 5.0); // Floating-point division
return 0;
}
Output:
3
3.400000
Note: The modulo operator % cannot be used with floating-point types.
Example: Generating random numbers in a range
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char **argv)
{
// Seed random number generator
srand(time(NULL));
for (int i = 0; i < 5; i++)
{
// Generate number between 1 and 100
printf("%d ", rand() % 100 + 1);
}
printf("\n");
return 0;
}
Output:
42 87 15 93 28
Example: Generating random uppercase letters
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char **argv)
{
srand(time(NULL));
for (int i = 0; i < 5; i++)
{
printf("%c ", rand() % 26 + 'A');
}
printf("\n");
return 0;
}
Output:
X M R K P
8.2 Compound Assignment Operators
Important: The right-hand side expression is evaluated as a complete unit before assignment.
Example:
#include <stdio.h>
int main(int argc, char **argv)
{
int x = 2;
x *= 5 + 7; // Equivalent to x = x * (5 + 7)
printf("x = %d\n", x);
return 0;
}
Output:
x = 24
8.3 Relational Operators
| Operation | Symbol |
|---|---|
| Greater than | > |
| Less than | < |
| Equal to | == |
| Less than or equal | <= |
| Greater than or equal | >= |
| Not equal | != |
8.4 Logical Operators
| Operation | Symbol |
|---|---|
| Logical AND | && |
| Logical OR | || |
| Logical NOT | ! |
Logical AND short-circuit: For A && B, if A evaluates to false, B is not evaluated.
Logical OR short-circuit: For A || B, if A evaluates to true, B is not evaluated.
In C, zero represents false, and any non-zero value represents true.
8.5 Bitwise Operators
8.5.1 Bitwise AND (&)
Truth table: Output 1 only when both inputs are 1.
Property: AND with 1 preserves the bit; AND with 0 clears the bit.
Use case: Clearing specific bits to zero.
Example: Clear bits 2 and 3 of an 8-bit value
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned char data = 0xff;
printf("Before: %#x\n", data);
data &= ~(0x01 << 2 | 0x01 << 3);
printf("After: %#x\n", data);
return 0;
}
Output:
Before: 0xff
After: 0xf3
8.5.2 Bitwise OR (|)
Truth table: Output 1 when at least one input is 1.
Property: OR with 0 preserves the bit; OR with 1 sets the bit to 1.
Use case: Setting specific bits to one.
Example: Set bits 2 and 3 of an 8-bit value
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned char data = 0x00;
printf("Before: %#x\n", data);
data |= (0x01 << 2 | 0x01 << 3);
printf("After: %#x\n", data);
return 0;
}
Output:
Before: 0x0
After: 0x0c
8.5.3 Bitwise NOT (~)
Inverts all bits: 0 becomes 1, 1 becomes 0.
8.5.4 Bitwise XOR (^)
Truth table: Output 1 when inputs differ (one is 1, the other is 0).
Property: XOR with 0 preserves the bit; XOR with 1 toggles the bit.
Use case: Toggling specific bits.
8.6 Shift Operators
Left shift (<<): Leftmost bits are discarded, zeros are shifted in from the right.
Right shift (>>): Rightmost bits are discarded, leftmost bits are filled with either zeros (logical shift) or sign bit (arithmetic shift) depending on the compiler.
8.7 Ternary Operator
Syntax: condition ? expression_if_true : expression_if_false
- Operator Precedence
Operator are evaluated in a specific order. Use parentheses to explicitly control evaluation order when in doubt.