# What should be included in a best practices C course?

At our University, we have in the first semester a very difficult C Introductory Course, that consists of presenting a shortened version of the language specification: What are for/while loops, if clause, what is a pointer and so on.

But they never show how good C code in a more complicated application should look. What version control is, what Valgrind is (a gnu tool for detecting memory leaks) and so on. For the many new (about 60%) to programming or C, the group projects are quite a knockout (about 40%).

Some motivated students and I decided to offer a "best practices" session before the first assignment is handed out and after the lecture has finished. For the students to have a better chance of finishing this course successfully.

Our selected Chapters are these:

• how to compile (clang and gcc and their flags and warnings)
• valgrind
• make
• coding style
• best practices -> how to allocate memory, typedefs, file operations
• basic program flow, some simple patterns
• git
• pitfalls specific to this course and C in general
• how to ask the right question (and in turn how to google their problems)
• how to use the manpage

Are there points missing? Is there something that we shouldn't do? In short, how to improve the content. We are planning on 3 Sessions with around 3 hours each.

• As a working software engineer I implore you: teach ideas rather than tools. E.g. how/why to use version control rather than focusing on git. – Jake Dec 21 '17 at 18:38
• If this is a first semester C course, your curriculum shouldn't contain any of the tools your propose. They need to know how to link, compile and debug. – Robert Harvey Dec 21 '17 at 19:16
• <shamelessPlug>Dear HNQ visitors, this question was asked yesterday, and is related enough to this one that it might also benefit from your expertise. </shamelessPlug> – Ben I. Dec 21 '17 at 19:23
• @gismo: Would the source control be maintained for them? Source control is not a mountain of detail I would want in my first-semester C class. Is this a two-semester class? See also Jake's comment above. – Robert Harvey Dec 22 '17 at 15:35
• What about gdb? A debugger allows students to reason about code interactively. – Winny Dec 22 '17 at 21:27

## Too much, too fast

"First semester". If I parse correctly, you say 60% students are new to programming.

You say "The following points knock them out: they dont know how to test, they dont know how to check for memory leaks, they have no concept of capsulating". And you say "group assignments".

Taken together, it looks like this course is "introductory" only in this sense it will introduce part of students to the idea that this university doesn't want to teach them (programming or even teach them how to learn programming), but it wants to filter them out.

You cannot change that fact. You cannot help everyone, you can help some.

Either you want:

• to help programmers to become better
• to help some non-programmers to become programmers (will work 10% of the time in your setting)
• to help all non-programmers to learn a bit, but probably not enough to let them pass

Presently your mini-course is a big mish-mash. Come on: someone needs to be informed how to google stuff? Really? And the next minute you talk about git? To help who, in what way...? Don't waste everyone's time, narrow it down:

• to help programmers to become better

• valgrind
• coding style
• best practices -> how to allocate memory, typedefs, file operations
• basic program flow, some simple patterns
• git
• pitfalls in general
• to help some non-programmers to become programmers (will work 10% of the time in your setting)

• you want to give them ready-made stuff; their heads are bound to explode, the more boilerplate you give them, the less the explosion
• make
• boilerplate gcc flags included, etc
• pitfalls specific to this course
• the rest will come later (i.e. someday they will decide they need to improve on memory handling - but not today for sure, someday they will decide to migrate version control out of their facebook - not today)
• to help all non-programmers to learn a bit, but probably not enough to let them pass
• "how to google their problems"
• "how to use the manpage"
• Welcome to CSEducators. A somewhat different perspective, but a dose of reality. Thanks. – Buffy Dec 22 '17 at 12:28
• Sadly, enrollments are so high at this moment (late 2017) that many departments are, indeed, trying to winnow the students. I don't know if that is the situation here, but it has been discussed elsewhere. This happens every few years, actually, and is often followed by a drought. The fact that many of the topics discussed in this thread were omitted from the course in question may be an indicator that this is the case, actually. – Buffy Dec 22 '17 at 12:31
• Thank you, for making clear what i did know deep down, but didnt really consciously knew. If nothing better pops up in the next few days, youll get the accepted answer. – Git Dec 22 '17 at 13:32

I think an important aspect to any best-practices list is the rationale behind it. It is entirely too common for a programmer to, for example, insist that gotos and global variables are evil and then proceed to use exceptions and singletons to create the exact same problems that got those features proscribed in the first place.

So I suggest that when introducing a rule of thumb, you don't just give examples of code that follows the rule, but rather examples of the sort of awful code that led to the rule being created. Let them understand why the rule exists both so they can avoid making similar mistakes and also recognize when the rule isn't applicable. (Of course, no examples will be as helpful as allowing them to write terrible code and then try to modify it, but you have limited time.)

A related recommendation that is somewhat tangential to the best practices lecture, but related to the question of how to help them complete the course successfully: Don't assume that the more abstract explanations of topics are necessarily easier to understand than the ones that get into the gritty details.

I've had a number of students who were completely confused when we tried to explain pointers using diagrams of boxes and arrows, but as soon as I sketched out a table of memory (with addresses as indices), explained that a pointer is just an integer that is used to "index memory", and then walked through a block of code, updating the table as I went, they understood it almost immediately. (We put the abstractions back in place when it was time to work with higher level data structures, after they understood the fundamentals.)

• @RobertHarvey I almost left it out of the answer, since it's not really a best practice, but I've never seen students move from "complete confusion" to "complete understanding" as quickly as I have after giving that explanation, so I decided it was worth mentioning anyway. – Ray Dec 21 '17 at 22:41
• Regarding explained that a pointer is just an integer that is used to "index memory", this can be a double-edged sword. It can lead new C programmers to think they're in on the secret about "how things really work", which is actually oversimplified and wrong, and thereby insist on being allowed to do very wrong/against-best-practices things. Even ptr+n becomes confusing for new C programmers when they think that ptr is an integer rather than has a representation as an integer. – R.. Dec 22 '17 at 19:02
• @R.. Whenever I need to simplify things, I let them know that I'm doing so. That's the best middle ground I've found between letting them believe that an approximate explanation is precise, and trying (and failing) to explain everything at once. That said, ptr is an integer. It's just not an int. But even int is a closer approximation of its type than &**magic[42]->&a, which is frequently how they seem to view it without this explanation, so I'd be fine with them thinking of it along those lines at first as long as they understand it's an approximation of how things really are. – Ray Dec 22 '17 at 19:14
• @Ray: The only sense in which it "is" an integer is that the bits of its representation can be interpreted as an integer, which is also true for floating point types, and I wouldn't say that they "are integers". For C implementations common in the past 2-3 decades with flat linear address spaces, as an implementation detail there's also a partial algebraic correspondence between pointers and integers. I say "partial" because many operations that are defined on the int side are not defined on the pointer side. But other implementations are certainly still possible... – R.. Dec 22 '17 at 19:31
• @supercat: If the compiler does (without you giving a flag to tell it to do so) an optimization the standard disallows, that's a bug, and vendors will fix it if reported. However, what is usually going on is that the compiler is simply a better rules lawyer than you, and it turns out the weird thing you wanted to do and convinced yourself is well-defined actually isn't. – user2681 Dec 23 '17 at 9:32

This might be controversial, but I would make a point to explain that goto is not always considered harmful (and explain that the context of Dijkstra's "Go To Statement Considered Harmful" was about using available control structures). In C, there aren't very good control structures for releasing resources; in the absence of them, goto works well, and people should not be afraid to use it for that purpose.

People new to C inevitably have trouble doing manual resource management and end up with memory (or other resource) leaks. Dealing with it is hard. People then think C is harder than it is. Trying to follow a single-entry, single-exit (SESE) pattern can reduce the cognitive burden and make code easier to maintain in the future.

For example:

char* foo(const char* directory)
{
char* path = make_full_path(directory, CONSTANT_FILENAME);
if (path == NULL)
{
return NULL;
}

FILE* fp = fopen(path, "r");
if (fp == NULL)
{
free(path);
return NULL;
}

char line[1024];
fgets(line, sizeof line, fp);

char* copy = malloc(strlen(line) + 1);
if (copy == NULL)
{
fclose(fp);
free(path);
return NULL;
}

strcpy(copy, line);

fclose(fp);
free(path);

return copy;
}


Simpler:

char* foo(const char* directory)
{
char* path = NULL;
FILE* fp = NULL;
char* copy = NULL;

path = make_full_path(directory, CONSTANT_FILENAME);
if (path == NULL)
{
goto exit;
}

fp = fopen(path, "r");
if (fp == NULL)
{
goto exit;
}

char line[1024];
fgets(line, sizeof line, fp);

copy = malloc(strlen(line) + 1);
if (copy == NULL)
{
goto exit;
}

strcpy(copy, line);

exit:
if (fp != NULL)
{
fclose(fp);
}
free(path);

return copy;
}


And imagine that we need to introduce some new, temporary allocation to the code. In the first version, that would require considering where the new allocation occurs and inspecting the various exit points to make sure that each exit point cleans up the new allocation if necessary. In the second version, it requires only adding the variable to the beginning, initializing it to a sentinel value (e.g. NULL), and then unconditionally freeing it at the end.

• Although very usefull, the lecturer actually substracts points for each use of of the "goto" statement. but thank you for a clean example how to actually use it. – Git Dec 22 '17 at 9:33
• Why not factor out the resource management as a separate function? – Buffy Dec 22 '17 at 20:54
• @Buffy Because they're local variables, and it writing separate structs and functions to deal with resource management for every combination of such variables does not scale well. – jamesdlin Dec 23 '17 at 1:24
• Nor does repeating code every time a given combination recurs. Especially if things change and you have to revisit every instance. Abstraction is your friend. – Buffy Dec 23 '17 at 1:38
• @Buffy Additionally, even if the resources could be freed by a single function, I would still advocate using goto. It's still easier to maintain; if you need to change the signature or semantics of you resource deallocation function, you'd won't need to revisit every exit point. Also, doing goto exit; at each exit point is simpler than FreeMyResources(...); return NULL; if you need to return something. – jamesdlin Dec 23 '17 at 7:28

Your list is a bit narrow in one sense. I assume it is well matched to your specific course, but probably doesn't represent "best practice" in general. For example, valgrind is limited to linux, which suits you better than me. But the idea of including memory testing, for example, is a good idea no matter the specific tool. Similarly for git. There are alternatives, but code management and version control is the big idea.

But one item I find missing here, but also essential is some sort of tool for unit testing. There are many available and building good clean code requires pre testing everything to arrive at a good result painlessly.

Another suggestion I'd make, though this may be your intent already, is that you show many of your ideas in the context of a large and complicated program. You state that dealing with such projects/programs is an issue in the course at the beginning of your post and that may actually be the biggest issue. So don't present your tool set using only "toy" programs.

If the projects are done in teams you may also want to include something specific about how to be successful in a team environment. Many of your students may not have experienced that. There is a book, in fact, named Teamwork is an Individual Skill that has valuable hints for any professional.

• We thought about using the final assignment from a previous year (around 800 LoC). Do you think that is big enough, or should we use a bigger project? If so, do you have one in mind? Since most open source projects that i have had a look on the code, have terrible code documentation. – Git Dec 21 '17 at 14:05
• If that is the scale of the assignment to come then it is probably a good choice. Others may need something bigger in a similar context. It is probably a mistake to overwhelm students unnecessarily. And you may need to doctor whatever code you find before use. You could also use the sessions to improve the code, of course. That might be especially valuable in this context. You don't mention whether the students start the project with a given codebase or start from scratch. That can affect how you proceed, but the toolset would likely be the same. – Buffy Dec 21 '17 at 14:10
• They start without any codebase, just the standard libraries. – Git Dec 22 '17 at 9:39

## Lint

I would not program in C without a lint tool e.g. gcc -Wall or pclint/flex-lint, unfortunately the latter two are proprietary.

However you need to show that error/warning messages are your friend. There are not an accident. Someone spent time writing them, to help you. Read them, and fix the underlying problem. Often I have seen people finding ways to get the error/warning to go away, but changing the code in a convoluted way, that makes it worse, but has no error.

## Brackets {} are not an option.

I don't care what the standard says, always use brackets.

Well written code with good names, is better than code with bad names and comments. Use procedure/function names, and variable names to comment your code.

i++; /*Increment i*/
i++; /*Increment index*/


### Better

index++;


## Local consistency

Style must be locally consistent, and preferably globally consistent. Global consistency may suffer if there are more than one person on the team. But local consistency must never suffer.

Variable name should be nouns or for booleans adjectives. Procedures should be verbs. Functions should be as for variables.

Full command/query separation is not easy in C as you would have to program Object Oriented.

• C was created at Bell Labs. If you worked there it was a "firing offense" if you didn't brace all. I.E. Brackets are NOT an option. Make your fingers do it so you don't need to even think about it. If you have a good IDE, the close bracket will be correctly placed if you open a brace and hit return. – Buffy Dec 21 '17 at 17:27
• I would also add a note about "Global Variables" and "Code Reusability" to your list. – jjk_charles Dec 22 '17 at 0:40
• My \$0.03: probably i is well-known enought not to adding much information over index. Similarly no points for guessing what v and a are in physics simulator. pos[i] += v[i] * dt; v[i] += a[i] * dt; is probably clearer than positionOfObjectsArray[index] += velocityOfObjectArray[index] * timeDifference; velocityOfObjectArray[index] += accelerationOfObjectArray[index] * timeDifference;. – Maciej Piechotka Dec 22 '17 at 9:51
• A couple major points of disagreement. First, comments, both are bad. Commenting i++ to explain what the statement does is not useful, it's clutter and trains new programmers not to actually be intentional about good comments. A good comment might be something like /* Skip an additional element because the code below assumes... */ or similar. Comments are to explain purpose, not to translate C to English. – R.. Dec 22 '17 at 19:05
• Yes use abbreviation, if they are used in the domain, e.g. physics. OR tv for television, because these are well known, but don't make up new ones. – ctrl-alt-delor Dec 23 '17 at 12:31

Note:

I was under the impression that this question was by a faculty asking for advice on best practices for teaching a course on C. Instead it's a question by a proactive student that is disappointed with the department's approach and wants to help educate his peers on best practices.

I believe that my answer is wrongly targeted but I'm leaving it here as I feel that it might hold some value to others.

--

When I went to school for programming our department had a very interesting approach which, in my opinion, helped to mitigate a lot of the confusion surrounding a first introduction.

Provide a very rigid style guide

• We had department-wide coding guidelines which were adhered to for all courses (much like when working for a company) so not only was code between students fairly consistent but also consistent between assignments for the same student. This is often overlooked and is in my opinion one of the most important things when teaching what literally amounts to be a foreign language.

Provide some form of static analysis for both style and logic

• Every assignment was passed through a "style checker" written by the department that verified a combination of style conformity and basic linting for static error checking. This further reinforced adhering to a single style of coding and the linting was helpful and helped us to not be afraid of output warnings and errors.

Provide unit tests so that students can focus on the implementation rather than the formatting

• Every assignment to be submitted was run through unit tests that verified input/output. This was tremendously helpful as it allowed us to spend less time on the math and more on the implementation and practical application of the code we were writing to solve a problem.

Focus on a standard and cross-platform tool-chain and leverage it to teach principals that are applicable to other environments; teach them the language not an IDE

• Compilers and the aforementioned tools were available only from the department "Linux lab" computers. We were tasked with learning the command line and text editor of our choice (nano/emacs/vi) and compilation was done directly at the command line with g++. We were also taught how to ssh into the Linux lab so that we could work off-campus. This provided a simple environment without all of the extraneous features of IDEs and a basic introduction to both a Unix-like environment and command line compiling.

Put blinders on your students and choose to use a subset of the language to force students to think about solving problems with the language itself rather than through a library

• All classes for teaching programming principals stripped everything to the bare minimum so that we were learning not only the core of the language (C++ in this case) but eschewed even the STL; for example, we programmed all of our data structures by hand. We learned the tools and the language which allowed us to later understand what a given library was doing under the hood rather than it being a black box.

Tell them where to go for resources

• It might seem like second nature to us but new students don't know where to go for resources and may not think beyond their textbook. Websites that provide an overview of the language, its keywords and headers, such as http://en.cppreference.com/w/c/language that are easily browse-able on an internet device are excellent. Man pages are also useful but remember that this is a foreign concept so remember to demonstrate how to use it frequently during class. A small library of books as references can also provide additional insight on how to tackle a problem outside of a course textbook.

For those who are new to programming, this rigid conformity is essential to block out so much of the background noise and flip-flopping on coding style that is so prevalent for new students.

There was no question for us about where top put braces, semi-colons, white-space, and comments. There was no question about basic linting. There was no question about whether a program worked or not since it was run through unit tests.

I cannot stress enough how effective this approach was for me as a student. It gave us blinders that shielded us from the unimportant details and allowed us to focus on the most important things in each lesson and made learning to program a very satisfying and extremely rewarding experience.

• This answer is excellent, and tells me that you probably have quite a bit to contribute to this site (and I hope that you do!) Did the highly regularized style specifications make it more difficult to detect cheating? – Ben I. Dec 29 '17 at 4:20

Two important concepts that seem absent from the list, and that I often find are barely developed in even fairly advanced students are testing and debugging. I would suggest some brief introduction along the following lines:

Unit test and integration test; test scaffolding; test-first strategy; exhaustive test vs. (targeted) random test vs. testing manually determined corner cases.

Use of assertions; logging, possibly at various debug levels; insertion of printf() calls for adhoc debugging; use of a debugger: break points, single-stepping, observing variables, watchpoints; code simplification to create an MCVE.

• Welcome to CSEducators. I agree that understanding assertions is an important idea. – Buffy Dec 21 '17 at 19:16

If you want to teach the students some valuable real life skills, helping them in writing programs without bugs, teach them about sequence points and integer promotion.

If a C programmer does not understand those concepts, he or she is bound to make some serious mistakes sooner or later.

#include <stdlib.h>
#include <stdio.h>

static int n = 100;

static int f1(void) {
n++;
return n;
}

static int f2(void) {
n++;
return n;
}

static int f3(int a, int b, int c, int d)
{
printf("Undefined behaviour allows the compiler to do anything!\n");
printf("n = %d, a = %d, b = %d, c = %d, d = %d\n", n, a, b, c, d);
return n + a + b + c + d;
}

int main(int argc, char *argv[])
{
unsigned int a = 10;
signed int b = -10;

if (a < b) {
printf("This is executed despite that (%u < %d) is not true\n", a, b);
}

n = f3(n, f1(), f2(), n++);

return EXIT_SUCCESS;
}


Compiled with gcc:

This is executed despite that (10 < -10) is not true
Undefined behaviour allows the compiler to do anything!
n = 103, a = 103, b = 103, c = 102, d = 100


Compiled with clang:

This is executed despite that (10 < -10) is not true
Undefined behaviour allows the compiler to do anything!
n = 103, a = 100, b = 101, c = 102, d = 102


Even if the students do not fully grasp those two concepts, being aware of them is important.

That's really a nice question and I appreciate that you consider the noise around programing like version control (git) and coding style.

View things important to me are:

Flowcharts: or the art of coding on a piece of paper. When I was a student, our teacher insisted on designing the logic on paper before typing it into the machine. Maybe this sounds old school but for larger projects I still take the pencil first. This helps to understand and focus on logic.

Algorithms: or defining the problem and finding a solution to it. Mostly C programs are likely command line programs and no full blown apps. So the best use and challenge for C is to learn concepts of Algorithms and Computing It's always fun to me to draw concepts on a whiteboard, this helps to get some imagination and creativity for the topic.

Debugging: or how to get to know what your machine is doing right now. Programming is like manipulating bits and bytes in the Memory (tag: Von Neumann architecture). To visualize what's going helps to understand what the machine is doing and much more help to find and understand your mistakes (semantic failures). With the debugger you can walk through your code and see variable and memory change. Especially when it comes to different types, arrays, pointers, and memory location (stack, heap, registers, etc.) it is good to know how to look that up.

If you like to teach "how to google their problem" it would be an advice to teach how to ask the right question

• >"ask the right question" thats what i meant =) – Git Jan 31 '18 at 10:41

if they are new to programming start with flip-flops. I usually refer to Lombardi's "This year, we are going to start from the beginning. This, gentleman, is a football". It makes it funnier when they are British.

You can delete this answer as I don't have comment permissions.

debian
eclipse-cdt
svn/git
cmake
jenkins
unit test


250,000 lines of code is more reasonable than 800. Also, if you need to add a comment to code it means that the code is unintelligible.

Edit:

As requested in the comment. I think starting with a brief introduction to electrical engineering and discreet mathematics is appropriate in an introductory programming course. The list above is not complete or accurate, but it's something that can be setup in a day and increases the productivity of other programming tasks that are part of your lesson.

Also, implementing strcpy is a good task for anyone from an arts background.

• Welcome to CSEducators. You should really expand a bit on your answer, explaining a bit about why you believe each is important. Longer and more complete answers are valued over short ones. – Buffy Dec 21 '17 at 17:40
• While I agree that a bigger program might be desirable in general, in the writer's context, the short one, if it better matches the local need is probably better. Especially if old questions to be addressed have approximately that length. – Buffy Dec 21 '17 at 17:42
• Interesting profile you have, also. – Buffy Dec 21 '17 at 17:43
• @Buffy thank you. I'll add a bit, I really just wanted to comment but I don't have permissions on this site – Abdul Ahad Dec 21 '17 at 17:49
• If you give a better answer, you'll probably earn rep here and the comment ability will flow forth. – Buffy Dec 21 '17 at 17:53

It's important to teach that the name C refers to two diverging languages--a low-level language which is useful for systems programming because many operations which different platforms may handle differently are handled in a documented fashion characteristic of the actual execution environment, and a more recent high-level-only language which uses the same syntax as the low-level language, but which allows compilers to behave in arbitrary fashion if code attempts certain operations whose behaviors would be defined in the former language.

Given a piece of code like:

unsigned mul_mod_65536(unsigned short x, unsigned short y)
{
return (x*y) & 0xFFFF;
}


compilers for the former language would multiply the values of x and y using whatever semantics the platform uses for integer multiplication, take the bottom 16 bits of that result, and return them. On platforms where integer overflow would yield a result whose bottom 16 bits is correct, the above would yield the correct mod-65536 sum for all combinations of x and y. The published rationale for C89 indicates that the authors of the Standard would have expected such behavior from most current (and IMHO presumably future) compilers.

On compiler processing the latter language, however, the above code may sometimes malfunction in totally nonsensical ways if the value of x*y would exceed 2147483647. If, for example, code were to call mul_mod_65536(i,65535) some "modern" compilers would use the multiplication to infer that i cannot be greater than 32768, and thus "optimize out" code elsewhere in the program that would only be relevant if it were. Thus, unlike some languages which specify that an overflow will wrap cleanly (like Java), or specify that it will trap (like C#, within checked contexts), "modern C" requires that programmers absolutely positively prevent overflows from occurring under any circumstances even in cases where one of the above behaviors would suffice (or even in circumstances where either would be equally useful).

Programmers need to be aware that there exists a lot of code which is written for the former kind of C, and also that some compilers like gcc and clang will be incompatible with such code unless explicitly forbidden from applying aggressive "optimizations".