Assignment #1:

📚 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:

  1. What is the final value of iCount after the loop finishes?

  2. Why does the loop eventually stop, even though i is an unsigned type?

  3. After changing i from unsigned short to unsigned int, how does the loop’s behavior change?

  4. Explain in your own words what “integer wraparound” means in C.

3️⃣ Part_A - Scope, For‑Loops, Unsigned Types, printf

Questions:

  1. Why is unsigned int used for the loop variable in this part?

  2. Why is the initial value written in hexadecimal (0x8000) instead of decimal or binary?

  3. Compare this C for loop to a C# for loop. What is similar? What is different?

  4. Could the statement iCount++ be moved into the for header?

  5. If yes, rewrite the loop header (in a comment) showing how it would look.

  6. Where does the printf output appear when you Build/Run the program?

  7. Where does the printf output appear when you Build/Debug the program?

4️⃣ Part_B — Bitwise Operations & Masks

🔧B1 — Clearing Bits with a Mask

Questions

  1. What is the final value of i after the loop completes?

  2. Describe, in plain language, what the loop does to the lower four bits of i.

🔧B2: Clear First and Last Bits

Questions

  1. Write the code that clears bit 0 and bit 7 of i without affecting any other bits.

  2. What mask(s) did you use, and why?

  3. 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 i is always set to 1.

  • Why is the OR (|) operator the correct choice for this task?

🔧B4 - Count Cleared Bits

Questions

  1. Write code that counts how many bits in i are zero.

  2. What logic did you use to test each bit?

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

  1. Write code that modifies i so that exactly two bits remain set.

  2. Describe your algorithm in 2–3 sentences.

  3. After testing with multiple initial values of i, does your solution always produce exactly two set bits?

5️⃣Part_C — Structs & Pointer Arithmetic

Questions

  1. 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?

  1. After executing pThing++, how did the pointer value change?

  2. By how many bytes did the pointer increase?

  3. How is this change related to sizeof(SThing)?

  4. In your own words, explain why incrementing a pointer to a struct moves by the size of the struct rather than by 1 byte.