Chapter 3: Transition from C# to C

🧭 Transitioning from C# to C for Embedded System

1. 🧠 Mindset Shift: Managed vs. Bare Metal

  • C#: Runs on the .NET runtime, with garbage collection, exceptions, and rich libraries.
  • C: Runs directly on hardware (often with no OS), requiring manual memory management and tight control over resources. Key takeaway: You’ll need to think in terms of registers, memory addresses, and timing constraints.

2. Language Differences: C# vs. C

Feature C# Example C Example
Memory Management Automatic (Garbage Collector) Manual (malloc, free) We will avoid this in Embedded Systems
Exception Handling try/catch/finally Error codes, no built-in exceptions
Data Types Rich (List<T>, string) Primitive (int, char, arrays`)
OOP Support Full (classes, inheritance) None (structs, function pointers)
Libraries Extensive .NET libraries Minimal, often vendor-specific

3. ⚙️ Embedded-Specific Concepts to learn

  • Memory-mapped I/O: Registers are just addresses
  • Startup sequence: Reset handler, stack pointer, vector table

  • Bit manipulation: |, &, ^, <<, >>. Essential for manipulating registers and flags

  • Timers and Delays: Use hardware timers instead of Thread.Sleep.

  • Interrupts: NVIC, vector table, ISR registration (later on)

🧠 Some Syntax Differences: C# vs. C

🔢 Arrays: C# vs. C

Feature C# Example C Example
Declaration int[] nums = new int[5]; int nums[5];
Initialization int[] nums = {1, 2, 3}; int nums[] = {1, 2, 3};
Access nums[0] = 42; nums[0] = 42;
Length nums.Length sizeof(nums)/sizeof(nums[0])

Key differences:

  • C arrays are fixed-size and don’t carry metadata like .Length.
  • No bounds checking in C—accessing nums[10] is undefined behavior

🖨️ Output: Console.WriteLine vs. printf

Feature C# Example C Example
Basic Output Console.WriteLine("Hello"); printf("Hello\n");
Placeholder Console.WriteLine("Value: {0}", x); printf("Value: %d\n", x);
String Interpolation $"Value: {x}" Not supported

📏 printf Format Specifiers

Specifier Meaning Example Output
%d Signed integer 42
%u Unsigned integer 42
%x / %X Hexadecimal (lower/upper) 2a / 2A
%f Floating point 3.141593
%c Character A
%s String Hello
%% Literal percent sign %

You can also use width and precision:

printf("%6.2f", 3.14159); // prints "  3.14"

🔍 if Statement Evaluation: C# vs. C

Language Condition Type Example Behavior
C# Strict Boolean if (x > 0) Must evaluate to true or false
C Numeric Truthiness if (x) Non-zero is true, zero is false
int x = 5;
if (x) 
{
    // Executes because x ≠ 0
}

🔒 const in C# vs. C

Both in C# and C, const is used to define immutable values—things that shouldn’t change once set.

🧵 const in C#

const int maxValue = 100;
  • Type-safe and immutable.
  • Compile-time constant: Must be initialized with a literal.
  • Static by default: Belongs to the type, not the instance.
  • Cannot be assigned from runtime values.

🧵 const in C

const int maxValue = 100;
  • Type-safe: The compiler knows it’s an int.
  • Memory: May be stored in flash (ROM) if unused in RAM.
  • Scope: Obeys normal scoping rules (local, global).
  • Can be used with pointers.
  • In embedded systems, const is often used to place lookup tables or configuration data in flash memory

🧮 define in C

#define MAX_VALUE 100
  • Preprocessor directive: Replaced before compilation.
  • No type checking: Just a text substitution.
  • No memory usage: Doesn’t occupy RAM or flash.
  • Can be used for macros:
#define SQUARE(x) ((x)*(x))

Drawbacks:

  • No scope—global by default.
  • Can lead to tricky bugs if not parenthesized properly.
  • Not debuggable—doesn’t show up in symbol tables.

⚔️ Comparison Table: const vs. #define vs. C# const

Feature const in C #define in C const in C#
Type Safety ✅ Yes ❌ No ✅ Yes
Scope ✅ Scoped ❌ Global ✅ Scoped
Memory Usage ✅ May use memory ❌ None ✅ Stored in metadata
Compile-Time Value ✅ Usually ✅ Always ✅ Always
Debuggable ✅ Yes ❌ No ✅ Yes
Can Use with Pointers ✅ Yes ❌ No ❌ Not applicable

📏 sizeof Operator: C vs. C#

It returns the size of the argument in bytes

Feature C Example C# Example
Basic Usage sizeof(int) sizeof(int) (unsafe context only)
Returns Size in bytes Size in bytes
Works on Types ✅ Yes (sizeof(int)) ✅ Yes (sizeof(int))
Works on Variables ✅ Yes (sizeof(x)) ❌ No (sizeof(x) not allowed)
Requires Unsafe Block ❌ No ✅ Yes (unsafe { sizeof(int) })
Compile-Time Constant ✅ Yes ✅ Yes

🧠 Key Differences

  • In C:
  • sizeof works on both types and variables.
  • Always evaluated at compile time.
  • Commonly used for memory allocation and array sizing:
int arr[10];
int size = sizeof(arr) / sizeof(arr[0]); // Get array length
  • In C#:
  • Only works on value types (e.g., int, double) inside an unsafe block.
  • Cannot be used on variables or reference types.
    int size = sizeof(int); // OK
    // sizeof(obj); // ❌ Not allowed