Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A1: Implementing printf

Instructions: Remember, all assignments in CS 3410 are individual. You must submit work that is 100% your own. Remember to ask for help from the CS 3410 staff in office hours or on Ed! If you discuss the assignment with anyone else, be careful not to share your actual work.

The assignment is due via Gradescope at 11:59pm on the due date indicated on the schedule.

Submission Requirements

You will submit your completed solution to this assignment to Gradescope. You must submit:

  • my_printf.c, which will be modified with your solution for Task 1 and Task 2
  • test_my_printf.c, which will contain your tests for your solution for Task 1 and Task 2

Restrictions

  • You may not include any libraries beyond what is already included in my_printf.h
  • You may not use dynamic memory allocation (e.g. malloc and free).
  • You may add as many helper functions as you would like in my_printf.c (including those you wrote in Lab 1!), but you must leave the function signatures for my_printf and print_integer unchanged. You may not change my_printf.h, as we will be using our own header file for grading.

Provided Files

The provided release code contains six files:

  • my_printf.h, which is a header file that contains the required function definitions and some useful include statements. You may not modify this file. You may also not include any libraries in your implementation beyond what is already included in this file.
  • my_printf.c, which contains the function definitions for your implementation. This is where you will write your code for my_printf and print_integer.
  • test_my_printf.c, which is a test file with a couple test cases to get you started. You must add more tests to receive full credit for this assignment.
  • test_my_printf.txt, which is a text file that you can use to compare your outputs to by “diff” testing. See more in Running and Testing.
  • smoke_test.c and smoke_test.txt, which are the smoke test we run on your submission and its expected output.

Getting Started

To get started, obtain the release code by cloning the a1 repository from GitHub:

git clone git@github.coecis.cornell.edu:cs3410-2026fa-student/<YOUR NET ID>_a1.git
  • Note: Please replace the <YOUR_NET_ID> with your NetID. For example, if you NetID is zw669, then this clone statement would be git clone git@github.coecis.cornell.edu:cs3410-2026fa-student/zw669_a1.git

Overview

In this assignment you will implement your own version of printf (see the documentation here) called my_printf from scratch. Recall that printf works by taking in a format string that contains various format codes, in addition to a variable number of other arguments. The format codes specify how to “plug in” the arguments into the format string, to get the final result. For example:

printf("I love %d!", 3410); // prints "I love 3410!"
printf("Hello, %s", "Alan"); // prints "Hello, Alan"
printf("Hello %s and %s!", "Alan", "Alonzo"); // prints "Hello Alan and Alonzo!"

You will implement two key functions:

  • print_integer(int n, int radix, char *prefix): Print the integer n to stdout in the specified base (radix), with prefix immediately before the first digit.
  • my_printf(char *format, ...): Print a format string with any format codes replaced by the respective additional arguments.

Your implementation will be contained in my_printf.c. We’ve provided you with the function signatures to get you started. You should look at my_printf.h for detailed function specifications.

Assignment Outline

  • Task 1: You will implement the print_integer function
  • Task 2: You will implement the my_printf function

Implementation

Task 1: print_integer

Note

For Task 1 and Task 2, all your code should be in the “a1” Git repository. See the Getting Started section for how to retrieve the starter code. Your implementation will be contained in my_printf.c and test_my_printf.c.

If you would like to use the print_digit and print_string functions that you wrote in Lab 1, you should copy and paste them into my_printf.c from your lab1.c file that you implemented for Lab 1.

The print_integer function takes a number, a target base, and a prefix string and prints the number in the target base with the prefix string immediately before the first digit to stdout. radix may be any integer between 2 and 16 inclusive. For values of radix above 10, use lowercase letters to represent the digits following 9 (since bases higher than 10 canonically use lowercase letters as well).

This function should not print a newline. Here are some examples:

  • print_integer(3410, 10, "") should print “3410”
  • print_integer(-3410, 10, "") should print “-3410”
  • print_integer(-3410, 10, "$") should print “-$3410”
  • print_integer(3410, 16, "") should print “d52”
  • print_integer(3410, 16, "0x") should print “0xd52”
  • print_integer(-3410, 2, "0b") should print “0b11111111111111111111001010101110”
  • print_integer(-3410, 16, "0x") should print “0xfffff2ae”
  • print_integer(3410, 5, "") should print “102120”
  • print_integer(-3410, 5, "") should print “32244002321021”

For the radix 10, negative numbers should be printed with a negative sign (-). All other bases should use the 2’s complement representation from lecture. In other words, it should not print a negative sign, and instead just print an unsigned integer representing a 2’s complement number. This is exactly what printf from the standard library does when you pass in negative integers for bases other than 10. You can try this on your own:

#include <stdio.h>

int main() {
    printf("-10 in hex is: %x\n", -10);
    printf("-10 in binary is: %b\n", -10); // Note: requires C23
}

The above code outputs:

-10 in hex is: fffffff6
-10 in binary is: 11111111111111111111111111110110

which is the 2’s complement representation of -10 in hex and binary, respectively.

Warning

You are not allowed to call any functions from the C standard library except for fputc anywhere in your implementation. You should print a character to the console using fputc(c, stdout), where c is the character you want to print.

Tip

In addition to the documentation on cppreference.com, you can also find documentation for many standard library functions in C through the manual pages (“manpages”) in your terminal. Simply type:

man fputc

to pull it up. You can scroll through it and then type q to exit.

On our platform, an int is 32 bits, and you may assume this in your solution. Dynamic memory allocation (malloc) is prohibited, as stdlib.h is not allowed, but you may use fixed-size local variables and arrays. Other string manipulation or streaming functions are also prohibited, such as strcpy, strncpy, strcat, strchr, strtok, sprintf, printf.

Task 2: my_printf

This function prints format with any format codes replaced by the respective additional arguments, as specified below:

Your my_printf function is required to support the following format codes:

  • %d: integer (int, short, or char), expressed in decimal notation, with no prefix.
  • %x: integer (int, short, or char), expressed in hexadecimal notation with the prefix “0x”. Lowercase letters are used for digits beyond 9
  • %b: integer (int, short, or char), expressed in binary notation with the prefix “0b”.
  • %s: string (char*)
  • %c: character (int, short, or char, between 0 and 127) expressed as its corresponding ASCII character
  • %%: a single percent sign (no parameter)

For each occurrence of any of the above codes, your program shall print one of the arguments (after the format) to my_printf(...) in the specified format. Anything else in the format string should be expressed as is. For example, if the format string included "%z", then "%z" would be printed. Likewise, a lone “%” at the end of the string would also be printed as is (note that this differs slightly from the behavior of printf).

Note that strings in C can be NULL. If my_printf is passed a null string as an argument, it should not crash, but instead print (null) to represent the would-be string:

#include <stdio.h>

int main(int argc, char* argv[]) {
  my_printf("Null string: %s", NULL); // Prints: "Null string: (null)"
}

Again, you are not allowed to call any C standard library functions. You should print to stdout only using fputc (documentation for fputc is here).

For any format codes relating to numbers, your program should handle any valid int values between INT_MIN and INT_MAX, inclusive.

Note that my_printf is a variadic function, meaning it takes in a variable number of arguments. You don’t need to know this deeply, but you will need to look up the syntax, and also understand how a program determines the number of arguments.

A variadic function is any function that takes in an unknown number of optional parameters. The optional parameters are represented by three dots (e.g. int foo(int n, ...)). The dots are a part of the C language. The optional arguments are accessed using va_arg from stdarg.h. You must call va_start at the start of your variadic function before the first use of va_arg. You must call va_end once at the end of your variadic function, after the last use of va_arg. There is no way to know from va_arg how many optional arguments there are, so you need to use some other information to determine how many times to call va_arg. In this case, it is the format string. Here’s an example from the GNU documentation:

#include <stdarg.h>
#include <stdio.h>

int add_em_up(int count,...) {
  va_list ap;
  va_start (ap, count);         /* Initialize the argument list. */

  int sum = 0;
  for (int i = 0; i < count; i++)
    sum += va_arg (ap, int);    /* Get the next argument value. */

  va_end (ap);                  /* Clean up. */
  return sum;
}

int main(int argc, char* argv[]) {
  /* This call prints 16. */
  printf("%d\n", add_em_up (3, 5, 5, 6));

  /* This call prints 55. */
  printf("%d\n", add_em_up (10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

  return 0;
}

Here are some examples to help you understand the spec:

  • my_printf("3410") should print “3410”
  • my_printf("My favorite class is %d", 3410) should print “My favorite class is 3410”
  • my_printf("%d in hex is %x", 3410, 3410) should print “3410 in hex is 0xd52”
  • my_printf("The pass rate in 3410 is 100%%") should print “The pass rate in 3410 is 100%”
  • my_printf("Professor %s and Professor %s are the instructors", "Weatherspoon", "Susag") should print “Professor Weatherspoon and Professor Susag are the instructors”

Note that insufficient parameters could lead to undefined behavior (i.e. when the number of arguments is less than the number of format codes). You do not have to handle this case. Similarly, mismatched parameters (when the format code does not match the given argument’s type) can also lead to undefined behavior, but you do not need to handle this.

You are encouraged to use print_integer in my_printf. Nonetheless, these functions will be tested independently.

Running and Testing

Note

Like many commands on this page, this assumes you have the rv aliases setup as described in our RISC-V Infrastructure setup guide.

To compile your code, run:

rv gcc -Wall -Wextra -Wpedantic -Wshadow -std=c17 -o test_my_printf test_my_printf.c my_printf.c

Then, to run your code:

rv qemu test_my_printf

We will be testing your code by comparing the output of your program to a test file. You will extend the file test_my_printf.txt with your own test cases. You are required to write more tests, and the quality of the tests will be graded. Feel free to use the examples in this handout as a starting point.

To receive full credit for testing, you should have at least 10 test cases each for print_integer and my_printf. Test cases should cover as many paths through your code as possible. To receive full credit for testing for print_integer, you should have at least:

  • One test representing integers for each base from 2-16
  • One or more tests for different prefixes
  • One or more tests with no prefixes
  • One or more tests with positive numbers
  • One or more tests with negative numbers

To receive full credit for testing my_printf you should have at least:

  • One test for each format code
  • One test for no format codes
  • One test that contains multiple format codes

To compare the output of your program with the test file, run:

rv qemu test_my_printf > out.txt && diff out.txt test_my_printf.txt

If you don’t see any output from this command, your tests are passing. Note, for each test you add in test_my_printf.txt, you must call the corresponding function (either print_integer or my_printf) in test_my_printf.c. You should insert newlines between your test cases for readability. You may use printf in your test file, if you wish.

Don’t forget to recompile your code between different runs of your program.

Note, you can do this all in one command, like such:

rv gcc -Wall -Wextra -Wpedantic -Wshadow -std=c17 -o test_my_printf test_my_printf.c my_printf.c && \
    rv qemu test_my_printf > out.txt && \
    diff out.txt test_my_printf.txt

Submission

Submit my_printf.c and test_my_printf.c to Gradescope. Upon submission, we will provide a smoke test to ensure your code compiles and passes the public test cases.

Run smoke Test locally

You can build smoke test locally with the following command:

rv gcc -Wall -Wextra -Wpedantic -Wshadow -std=c17 -o smoke_test smoke_test.c my_printf.c

To test this code, you must execute the resulting file and pipe your print results to a file, such as with the following command:

rv qemu smoke_test > smoke_out.txt

Finally, you must compare the resulting prints to our expected results using diff:

diff --strip-trailing-cr smoke_out.txt smoke_test.txt

If you observe any differences between the two, a printing test failed.

You can also combine these operations into a single bash command:

rv gcc -Wall -Wextra -Wpedantic -Wshadow -std=c17 -o smoke_test smoke_test.c my_printf.c && \
    rv qemu smoke_test > smoke_out.txt && \
    diff --strip-trailing-cr smoke_out.txt smoke_test.txt

Rubric

  • 40 points: print_integer correctness
  • 50 points: my_printf correctness
  • 10 points: test quality