📚 Assignment 1 CMPE1250: Preprocessor Directives, Bit Manipulation & Structs
📋 Overview
In this activity you will:
- Use preprocessor flags to select which code section builds.
- Practice integer types, loops, and scope in C.
- Use the debugger to inspect variables and reason about control flow.
- Manipulate bits with masks and operators.
- Work with a simple struct and a pointer to it.
You will enable one part at a time, build, run under the debugger, and answer the questions in comments or in a separate text file
1️⃣ Getting Started
-
Create a new C project in your IDE (Segger Embedded Studio).
-
Add a single source file (e.g., main.c) and paste the code below.
-
Use the
#define Part_...lines to choose which section is active.
Copy the following code into your main.c file:
#include <stdio.h> #include <stdint.h> // we’ll use this later in the course
////////////////////////////////////////////////
// Enable exactly ONE part at a time
//#define Part_Demo
//#define Part_A
//#define Part_B
//#define Part_C
////////////////////////////////////////////////
// main is the entry point of your C program
int main(void)
{
// Preprocessor directives (like #ifdef) are handled
// before compilation. They let us include/exclude
// code based on simple flags.
#ifdef Part_Demo
//////////////////////////////////////////////////
// Part Demo – Instructor walkthrough
//
// This section is for an in‑class demo to show:
// - integer overflow / wraparound
// - using the debugger to inspect variables
//////////////////////////////////////////////////
int iCount = 0;
unsigned short i = 0xDEAD;
// Set a breakpoint on the line below and run to it.
while (++i)
{
++iCount;
}
// Use the debugger to determine:
// 1) What is the final value of iCount?
// 2) Why does the loop eventually stop, even though i is unsigned?
// Try changing the type of i from 'unsigned short' to 'unsigned int'.
// 3) How does that change the loop behaviour and iCount?
// Programs in embedded systems should never "fall out" of main.
while (1)
{
// idle loop
}
#endif // Part_Demo
#ifdef Part_A
//////////////////////////////////////////////////
// Part A – For loops, scope, and printf
//////////////////////////////////////////////////
{
// New block scope: variables declared here
// are not visible outside the braces.
int iCount = 0;
// TASK A1:
// - Explain (in a comment) why 'unsigned int' is used here.
// - Explain why the initial value is written in hex (0x8000).
//
// TASK A2:
// - Compare this 'for' loop to a C# for loop.
// What is similar? What is different?
for (unsigned int i = 0x8000; i; i >>= 1)
{
// TASK A3:
// - Could 'iCount++' be moved into the 'for' statement?
// If yes, rewrite the loop in a comment using that style.
iCount++;
}
// printf is a basic formatted output function.
// %d means "print this as a signed decimal integer".
printf("The loop ran %d times!\n", iCount);
// TASK A4:
// - Where does this output appear when you Build/Run?
// - Where does it appear when you Build/Debug?
// (Answer in comments or a separate text file.)
}
while (1)
{
// idle loop
}
#endif // Part_A
#ifdef Part_B
//////////////////////////////////////////////////
// Part B – Bitwise operations and masks
//////////////////////////////////////////////////
typedef unsigned char byte;
// B1: Bit clearing with a mask
{
byte i = 0b10100101;
for (byte mask = 1; i & 0x0F; mask <<= 1)
{
i &= (byte)~mask;
}
// TASK B1:
// - Use the debugger to find the final value of i.
// - In a comment, describe in plain language what this loop does
// to the lower 4 bits of i.
}
// B2: Clear first and last bit
{
byte i = 0xF7;
// TASK B2:
// - Write code so that after it runs, the first (bit 0)
// and last (bit 7) bits of i are cleared (0).
// - All other bits must remain unchanged.
// - Test with several different initial values of i
// using the debugger.
// Your code here
}
// B3: Force most significant bit to 1
{
byte i = 0x27;
// TASK B3:
// - Write code so that after it runs, the most significant bit
// (bit 7) of i is always set to 1.
// - All other bits must remain unchanged.
// - Test with several different initial values of i.
// Your code here
}
// B4: Count cleared bits
{
byte i = 0x27;
// TASK B4:
// - Write code that counts how many bits in i are 0.
// - Store the result in a variable (e.g., 'count').
// - Use the debugger to verify your result for several values of i.
// Your code here
}
// B5: Skill challenge – leave exactly two bits set
{
byte i = 0x01; // Example: final result should be 0x81
// TASK B5:
// - Write code that modifies i so that exactly two bits are set.
// - You should clear bits on the right until only two bits remain.
// - If necessary, "backfill" bits on the left so that the total
// number of set bits is exactly two, regardless of the initial i.
// - Test with several different initial values of i.
// Your code here
}
while (1)
{
// idle loop
}
#endif // Part_B
#ifdef Part_C
//////////////////////////////////////////////////
// Part C – Structs and pointers (preview)
//////////////////////////////////////////////////
// Define a simple struct type
typedef struct thing
{
int i;
float f;
char c;
} SThing;
// Create an instance of the struct on the stack
SThing aThing;
aThing.i = 42;
aThing.f = 3.14f;
aThing.c = ('A' + 'Z') / 2;
// TASK C1:
// - Use the debugger to inspect 'aThing'.
// - Note the addresses of each field (i, f, c).
// - Are they contiguous? In what order?
// A pointer is just a variable that holds an address.
SThing *pThing = &aThing;
printf("\r\nThe pointer is pointing at memory %p", (void *)pThing);
// Increment the pointer
pThing++;
printf("\r\nAfter increment, pointer is %p", (void *)pThing);
// TASK C2:
// - Use the debugger to see how pThing changed.
// - By how many bytes did the address increase?
// - How is that related to sizeof(SThing)?
while (1)
{
// idle loop
}
#endif // Part_C
// If no part is defined, main will just return.
// In embedded systems we normally avoid this, but
// for now it is acceptable while you are experimenting.
return 0;
}
2️⃣ Part_Demo — Integer Overflow & Debugger Warm‑Up
Questions:
-
What is the final value of
iCountafter the loop finishes? -
Why does the loop eventually stop, even though
iis an unsigned type? -
After changing
ifromunsigned shorttounsigned int, how does the loop’s behavior change? -
Explain in your own words what “integer wraparound” means in C.
3️⃣ Part_A - Scope, For‑Loops, Unsigned Types, printf
Questions:
-
Why is
unsigned intused for the loop variable in this part? -
Why is the initial value written in hexadecimal
(0x8000)instead of decimal or binary? -
Compare this
C for loopto aC# for loop. What is similar? What is different? -
Could the statement
iCount++be moved into the for header? -
If yes, rewrite the loop header (in a comment) showing how it would look.
-
Where does the
printfoutput appear when youBuild/Runthe program? -
Where does the
printfoutput appear when youBuild/Debugthe program?
4️⃣ Part_B — Bitwise Operations & Masks
🔧B1 — Clearing Bits with a Mask
Questions
-
What is the final value of
iafter the loop completes? -
Describe, in plain language, what the loop does to the lower four bits of
i.
🔧B2: Clear First and Last Bits
Questions
-
Write the code that clears bit 0 and bit 7 of
iwithout affecting any other bits. -
What mask(s) did you use, and why?
-
After testing with several values of
i, does your code work for all cases?
🔧B3 — Force the Most Significant Bit to 1
Questions
-
Write the code that ensures bit 7 of
iis always set to 1. -
Why is the OR (
|) operator the correct choice for this task?
🔧B4 - Count Cleared Bits
Questions
-
Write code that counts how many bits in
iare zero. -
What logic did you use to test each bit?
-
Verify your result using the debugger for several values of
i. Does it match your expectations?
🔧B5 — Skill Challenge: Leave Exactly Two Bits Set
Questions
-
Write code that modifies
iso that exactly two bits remain set. -
Describe your algorithm in 2–3 sentences.
-
After testing with multiple initial values of
i, does your solution always produce exactly two set bits?
5️⃣Part_C — Structs & Pointer Arithmetic
Questions
- Use the debugger to inspect
aThing.
• What are the memory addresses of i, f, and c?
• Are the fields stored contiguously?
• In what order do they appear in memory?
-
After executing
pThing++, how did the pointer value change? -
By how many bytes did the pointer increase?
-
How is this change related to
sizeof(SThing)? -
In your own words, explain why incrementing a pointer to a struct moves by the size of the struct rather than by 1 byte.