C Language Types and Expressions for Embedded Systems

  1. 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 bytes
  • typedef: Creates an alias for an existing type
  • volatile: Prevents compiler optimization

1.4.1 The volatile Qualifier

The volatile keyword serves two purposes:

  1. Forces the compiler to read the value from memory each time (not use cached value)
  2. 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:

  1. Define a variable using the original type
  2. Replace the variable name with the desired alias
  3. Add typedef at 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;
  1. 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:

  1. Single quotes surrounding a character indicate it should be treated as a character literal
  2. 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
  1. 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
  1. 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?

  1. Unique zero representation:``` +0: 0000 0000 -0: 0000 0000

  2. Simplifies arithmetic: Subtraction can be performed using addition

  3. 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:

  1. If bit 31 is 1: The value is interpreted as a negative two's complement number
  2. 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:

  1. If bit 7 is 1: Output is negative two's complement
  2. 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
  1. 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
  1. 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
  1. 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

  1. Operator Precedence

Operator are evaluated in a specific order. Use parentheses to explicitly control evaluation order when in doubt.

Tags: C Language Embedded Systems Data Types Variables Operators

Posted on Fri, 05 Jun 2026 17:42:16 +0000 by Wizard4U