Overview
Below are some of the general style qualities we expect your programs to have to receive full credit under the style criteria. Unfortunately, this is not an exhaustive list as there are a myriad ways to write ugly code. Please refer to each assignment spec for other style practices to follow if written.
In most professional work environments, if not all, you are expected to follow that company’s style standards and ways of writing documentation. Learning to follow a style guide, and writing code with a group of other developers where the style is consistent among them, are valuable job skills. This becomes increasingly more evident as the number of people who see your code increases and during the Penn Operating System
final project.
Disclaimer
As most things in academia and beyond, this document is a work in progress.
Goals
- Learn and apply consistent code styling principles.
- Understand the importance of readable, maintainable, and professional-grade C code.
- Develop skills for modularization, efficiency, and clean syntax in programming.
Contents
- Goals
- General Guidelines
- Whitespace and Indentation
- Naming and Variables
- Core C Statements
- Redundancy
- Efficiency
- Comments
- Functions and Procedural Design
- Testing Your Code
- Tools for Code Style and Memory Safety
- Submission Guidelines
General Guidelines
- Be readable: Other programmers should easily understand your logic.
- Just because you can read it, does not mean it is written well.
- Be modular: Break tasks into small, reusable functions.
- Avoid writing all your code within the entry point of a C program, the main function.
- Your main function should not contain all of your program’s functionality.
- Break down your code into modular functions with purposeful meaning.
- Avoid redundancy: Write helper functions for repeated code.
- If your code is identical in several places, this means it is best to write a helper function for that.
- Follow consistent styling conventions: Formatting, naming, and layout.
- Do not leave dead code, uncommented code, and non-descriptive comments throughout your files.
- If you find debugging statements to be imperative, use preprocessing directives to “activate” debugging.
- Leaving
printf
calls in a program is also violating style, as it allows for ambiguity in the output of a program. - Make sure your comments are descriptive when necessary; Do not write them in second person.
- Remove all Todos throughout source and header files.
- Make sure to error check all necessary functions. These will be indicated via an
\*
in the assignment spec.- No error checking will result in points deducted.
Whitespace and Indentation
-
Line Breaks:
- Add after
{
and avoid multiple statements per line. - Break long lines (>120 characters) into multiple lines by escaping the newline character.
- Add after
Bad Example:
if (a == b) { foo(); }
Good Example:
if (a == b) {
foo();
}
Bad Example:
#define long_string "The operating system is like the final layer in an onion. Except, when you hit the soil the onion is in. Then that's a problem for the Electrical Engineering Department."
Good Example:
#define long_string "The operating system is like the final layer in an onion. \
Except, when you hit the soil the onion is in. Then that's \
a problem for the Electrical Engineering Department."
Naming and Variables
- Use descriptive names (
firstName
,accountStatus
) and consistent conventions (camelCase
orsnake_case
) through your code. - Other courses are more specific to the style of variable names. We are not. As long as you are consistent.
- Declare variables in the narrowest possible scope.
- DO NOT MAKE VARIABLES GLOBAL UNLESS ABSOLUTELY NECESSARY.
- Use constants for literals. E.g. (
const int MAX_LENGTH = 100;
) or (#define EOF_INDICATOR -1
). - Be consistent in your types throughout your programs; do not randomly choose types.
Avoid Global Variables: Pass values through parameters or return them.
Bad Example:
int count; // Global variable!
int incrementCount(){
return count++;
}
Good Example:
int incrementCount(int count) {
return count + 1;
}
int main(){
int count = 0;
count = incrementCount(count);
}
Bad Example:
int variedForLoop(int param_one, uint32_t param_two, size_t param_three){
uint8_t flag = 0;
for(long i = param_one; i < param_two; i += param_three){
if(i == param_two){
flag = true;
}
if(flag) break;
}
return (int)flag;
}
Good Example:
bool variedForLoop(int loop_start, int loop_end, int step_size){
for(int i = loop_start; i < loop_end; i += step_size){
if(i == param_two){
return true;
}
}
return false;
}
Core C Statements
- Use
{}
for all control structures, even single-line blocks. - Choose
for
for definite loops andwhile
for indefinite loops. - Avoid redundant conditions in
if-else
blocks.
Bad Example:
if (x >= 80) { ... }
if (x >= 60 && x < 80) { ... }
Good Example:
if (x >= 80) {
...
} else if (x >= 60) {
...
}
Bad Example:
if (x == 0){
if(y ==1){
if(z == 0){
foo();
return;
}
buzz();
return;
}
buzz();
return;
}
buzz();
return;
Good Example:
if (x == 0 && y ==1 && z == 0){
foo();
return;
}
buzz();
return;
Redundancy
- Eliminate repeated code with helper functions.
- Factor out common code in
if-else
blocks.
Bad Example:
if (x < y) {
foo();
printf("Hi");
} else {
printf("Hi");
}
Good Example:
if (x < y) {
foo();
}
printf("Hi");
Efficiency
- Cache expensive calls in variables.
- Avoid unnecessary memory allocations.
- Use pointers to already existing data whenever possible.
- Do not allocate memory just to free it immediately after.
Bad Example:
for (int i = 0; i < strlen(str); i++) { ... }
Good Example:
int len = strlen(str);
for (int i = 0; i < len; i++) { ... }
Bad Example:
typedef struct _point{
int x;
int y;
} Point;
void init_point(Point* ptr) {
*ptr = (Point) {
.x = 0,
.y = 0,
};
}
int main() {
Point* p = malloc(sizeof(Point));
init_point(p);
printf("%d\n", p->x);
free(p);
}
Good Example:
typedef struct _point{
int x;
int y;
} Point;
void get_point(point* ptr) {
*ptr = (point) {
.x = 0,
.y = 0,
};
}
int main() {
point p;
get_point(&p);
printf("%d\n", p.x);
}
Bad Example:
int main() {
char* str = strdup("hello"); // assume STR must be on the heap for some reason.
// Capitalize the string;
char* cap = strdup(str);
for (size_t i = 0; i < strlen(str); i++) {
cap[i] = toupper(str[i]);
}
free(str);
str = cap;
}
Good Example:
int main() {
char* str = strdup("hello"); // assume STR must be on the heap for some reason.
// Capitalize the string in-place
size_t len = strlen(str);
for (size_t i = 0; i < len; i++) {
str[i] = toupper(str[i]);
}
}
Comments
-
Describe intent rather than obvious mechanics.
- Line by line comments are not necessary.
- Be placed at the file, function, block, or line level.
- Not be written in the second person unless absolutely necessary.
- Comments written in the second person are suspicious, as AI programs typically leave comments in this way.
- (e.g. “Or whatever signals you may need”, “Follows your guideline for X”, etc.)
- Do not leave commented out code.
1. File Header
Describe the file’s purpose, author, and any relevant details.
Moving forward, we will be actively looking for File Header comments as seen below. They don’t need to be verbose, but have an adequate amount of information sufficient to understand the purpose of the file.
Example:
/* CS5480 Assignment 0
* Author: Jane Doe
* Purpose: Implements a dynamic array in C.
*/
2. Function Header
Explain inputs, outputs, assumptions, and any edge cases. These should be written within the .h
file with implementation specific comments in the .c
- Yours do not need to be an exact match to this; but your header comments should be consistent across all files.
Example:
/* Function: incrementCount
* ------------------------
* Increments the given counter by 1.
*
* count: Current count value.
*
* Returns: Incremented count.
*/
int incrementCount(int count);
3. Block Comments
Provide insight into logical flow or significant chunks of code.
Example:
// Loop through the array to find the maximum value.
for (int i = 0; i < len; i++) {
...
}
// Reap all terminated child processes until there are none left.
// Do not block if none ready to be reaped.
while(waitpid(-1, NULL, WNOHANG)){
}
4. Line Comments
Explain dense or complex operations. Of course, do not write these if the code you write is self descriptive enough.
Example of redundant comment:
out = malloc(sizeof(buff)); // Allocate sizeof(buff) bytes
Example of good comment:
out = malloc(sizeof(header) + sizeof(point) * 2); // Allocating memory for a 'pair' of points with a header at the start.
Example of Bad Leftover Comments:
// TODO: Implement me!
int main(int argc, char *argv[]){
pid_t child = fork(); //ok don't forget to check if it's 0
if(child == 0){
//ok now we're in the kid.
printf("Let's check if we get here correctly. Here is the pid %d.\n", child);
//omg you totally forgot this will just print 0.
char *child_argv[] = {"sleep", "10", NULL};
execvp(child_argv[0], child_argv);
// we should not reach here but let's check
// printf("text here"); // ugh they don't want us to use printf. ok
const char *error_str = "error indicator\n";
write(STDERR_FILENO, error_str, strlen(error_str) + 1); //need to write to stand erro bc idk ????
exit(EXIT_FAILURE);
}
//wait what do we even do here??
waitpid(-1, NULL, 0); // Do we need flags?
// waitpid(child, NULL, WUNTRACED);
// waitpid(child, NULL, WNOHANG | WUNTRACED);
// waitpid(child, NULL, WNOHANG | WUNTRACED | WCONTINUED); i know it's one of these.
return;
sigsuspend(0); //?
}
Yes, we do see submissions like this. Please remove all comments that are not required, print statement, and dead code.
Functions and Procedural Design
- Functions should:
- Perform a single, coherent task.
- Avoid excessive length (>40-50 lines).
- Store data at the narrowest possible scope.
- Use const parameters when data should not be modified.
- Return results directly instead of using pointers, when possible.
Bad Example:
void max(int a, int b, int *result) {
*result = (a > b) ? a : b;
}
Good Example:
int max(int a, int b) {
return (a > b) ? a : b;
}
Bad Example:
void signal_sigint(int signal){
//signal stuff
}
void signal_specific_installer_SIGINT() {
struct sigaction sa = {0};
sa.sa_flags = SA_RESTART;
sa.sa_handler = signal_sigint;
sigaction(SIGINT, &sa, NULL);
return;
}
Good Example:
void signal_sigint(int signal) {
// Handle SIGINT signal
}
void install_handler(int signal, int flags, sigset_t blocked_on_entry,
void (*handler)(int), struct sigaction *previous) {
struct sigaction sig_act_struct = {
.sa_flags = flags,
.sa_handler = handler,
.sa_mask = blocked_on_entry
};
sigaction(signal, &sig_act_struct, previous);
}
Tools for Code Style and Memory Safety
- clang-tidy: Identifies code style issues.
- clang-format: Ensures consistent formatting.
- Valgrind: Detects memory leaks and errors.
Run these tools regularly during development:
make format
make tidy-check
valgrind ./your_program
Submission Guidelines
- Organize files into a clear structure if possible:
─project-directory ├─main.c ├─helpers.c ├─helpers.h ├─Makefile
- Ensure no compiler warnings or style violations.
- Push your code to the course-provided GitHub repository/submit on Gradescope!
- Do not submit .o files unless absolutely necessary/or an inherent aspect of the assignment.
Just in case you missed it above; Do not submit .o files unless absolutely necessary/or an inherent aspect of the assignment.
Attribution
This document is inspired by the style guidelines provided in many of the CS classes at Stanford University. Adapted for this course by Travis McGaha and Joel Ramirez.