26

In September, I will give my first lectures on C to students in engineering school (usually I teach math and signal processing, but I have also done a lot of practical work in C, without giving the lectures). Computer science is not their main topic (they are more studying electronics and signal processing), but they need to have a good background in programming (some of them will maybe become software developers)

This year will be their second year of learning C (they are supposed to know what a pointer is and how to use it, but of course, this notion is not yet assimilated)

In addition to the classical stuff (data structures, classical algorithms, ...), I will probably focus some of my lectures on:

  • Design the algorithm (and write it in pseudo-code) before coding it in C (think before coding)
  • Make your code readable (comments, variable names, ...) and
  • Pointers, pointers, pointers! (what is it, how and when to use it, memory allocation, etc...)

According to your experience, what are the most important notions in C that your teachers never taught you? On which particular point should I focus?

For example, should I introduce them to some tools (lint, ...)?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ThibThib
  • 8,010
  • 3
  • 30
  • 37

55 Answers55

34

Use of const keyword in pointers context:

The difference between following declarations:

 A)   const char* pChar  // pointer to a CONSTANT char
 B)   char* const pChar  // CONSTANT pointer to a char
 C)   const char* const pChar  // Both

So with A:

const char* pChar = 'M';
*pChar = 'S'; // error: you can't modify value pointed by pChar

And with B:

char OneChar = 'M';
char AnotherChar = 'S';
char* const pChar = &OneChar;
pChar = &AnotherChar; // error: you can't modify address of pChar
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matthieu
  • 2,743
  • 19
  • 21
  • Would you be kind enough to add the differences or english translations as comments next to each of them? Would make for a more educational answer. – Jorge Israel Peña Aug 10 '09 at 18:55
  • @Blaenk : You are right, I was a bit lazy, that's done ! – Matthieu Aug 10 '09 at 20:16
  • I recommend extending this to how to parse type declarations, from using `const` with pointers to the complex "`char *(* const(*a[8])())()[];`" (adapted from C FAQ 1.21). – outis Aug 11 '09 at 00:24
  • Don't forget char const* pChar // pointer to a CONSTANT char - same as const char* pChar char const* const pChar // Both – Stephen Nutt Aug 16 '09 at 01:57
32

My teachers spent so much time teaching us that pointers are scary little goobers that can cause lots of problems if not used correctly, that they never bothered to show us how powerful they can really be.

For example, the concept of pointer arithmetic was foreign to me until I had already been using C++ for several years:

Examples:

  • c[0] is equivalent to *c
  • c[1] is equivalent to *(c + 1)
  • Loop iteration: for(char* c = str; *c != '\0'; c++)
  • and so on...

Rather than making students afraid to use pointers, teach them how to use them appropriately.

EDIT: As brought to my attention by a comment I just read on a different answer, I think there is also some value in discussing the subtle differences between pointers and arrays (and how to put the two together to facilitate some pretty complex structures), as well as how to properly use the const keyword with respect to pointer declarations.

jeremyalan
  • 4,658
  • 2
  • 29
  • 38
  • 4
    Personally, as a teacher, I think teachers should teach BOTH. students need both to understand the power of pointers, but they also need to respect them. Beginners SHOULD be a little afraid of pointers, but not so afraid that they never use them and then actually understand them... – Brian Postow Aug 10 '09 at 15:16
  • 13
    @Brian: I can't disagree more. Fear keeps students from learning new things. They'll get the respect for pointers after fixing their first few memory leaks, segfaults and out-of-bounds errors, but they'll never get that far otherwise. – Paul Biggar Aug 10 '09 at 16:53
  • Same thing with me. I did not understand pointer until I learned assembler... – del-boy Aug 10 '09 at 18:15
  • @Paul: I also agree with you. I learned pointers fairly early on, never learned to be afraid of them, learned to use them in fun ways with graphics and memory mapped locations (think mode 13h stuff from the early 90s), and am still a bit weary of references and high level languages that hide such details (am I _sure_ that hasn't been gc'd? Why can't I just free it all, since I'm done with it -- and don't want huge memory fragmentation issues, etc.). What's the worst that can happen? A crash? Data corruption? Big deal -- better to learn to test and deal with it that be afraid for life. – lilbyrdie Aug 10 '09 at 20:22
  • 1[c] is also equivalent to *(c + 1) http://codepad.org/kGapFZaG – Liran Orevi Aug 12 '09 at 05:23
  • @Liran, you are evil! :) – kenny Aug 12 '09 at 10:52
  • It always bugs me when I see &c[2] in code instead of (c+2) or even worse: &c[0] – Harvey Aug 12 '09 at 16:19
19

They really should learn to use helper tools (i.e. anything other than the compiler).

1) Valgrind is an excellent tool. It's phenomenally easy to use and it tracks down memory leaks and memory corruption perfectly.

It'll help them understand C's memory model: what it is, what you can do, and what you shouldn't do.

2) GDB + Emacs with gdb-many-windows. Or any other integrated debugger, really.

It'll help those that are to lazy to step through the code with pencil and paper.


Not really restricted to C; here's what I think they should learn:

1) How to properly write code: How to write unmaintainable code. Reading that, I found at least three crimes I was guilty of.

Seriously, we write code for other programmers. Thus, it's more important for us to write clearly than it is to write smartly.

You say your students aren't actually programmers (they're engineers). So, they shouldn't be doing tricky things, they should focus on clear coding.

2) STFW. When I started programming (I started in Pascal, than moved to C), I did it by reading books. I spent countless hours trying to figure out how to do stuff.

Later on, I found that everything I had had to figure out had already been done by many others, and at least one of them had posted it online.

Your students are engineers; they don't have as much time to devote to programming. So, the little time they have, they should spend reading other people's code and, maybe, brushing up on idioms.


All in all, C's a pretty easy language to learn. They'll have a lot more trouble writing anything longer than a few lines than they'll have learning independent notions.

scvalex
  • 14,931
  • 2
  • 34
  • 43
13

When I had to use C as part of a larger project in school it was the ability to use gdb properly (i.e. at all) that ended up predicting who would finish their project and who would not. Yeah if things get crazy and you have tons of pointer and memory related bugs gdb will show weird information but even knowing that can point people in the right direction.

Also reminding them that C isn't C++, Java, C#, etc. is a good idea. This comes up most frequently when you see someone treating a char* like a string in C++.

Jon
  • 2,085
  • 2
  • 20
  • 28
  • Being able to use gdb (even just breakpoints and printing variables/registers) has saved me countless times. In fact, gdb leaves its mark the most when you don't have it (I'm looking at you, SML/NJ). – Andrew Keeton Aug 10 '09 at 20:19
  • True story: I had a job interview where one of the questions was "what is the first thing you'd do if the program you're writing crashes unexpectedly?". My answer of "run gdb and get a backtrace" was apparently impressive to the interviewer, and that's a pretty sad commentary on the other candidates. – Tyler McHenry Aug 11 '09 at 09:57
13

unsigned vs signed.

Bit shift operators

Bit masking

Bit setting

integer sizes (8-bit, 16-bit, 32-bit)

Robert Deml
  • 12,390
  • 20
  • 65
  • 92
11

Object orientation:

struct Class {
    size_t size;
    void * (* ctor) (void * self, va_list * app); // constructor method
    void * (* dtor) (void * self);                // destructor method
    void (* draw) (const void * self);            // draw method
};

(Code source)

Paul Biggar
  • 27,579
  • 21
  • 99
  • 152
  • 6
    +1 for noting that you can do OO without explicit support for it in your language. – Jon Aug 10 '09 at 14:50
  • I still remain confused why you'd artificially use C++. – GManNickG Aug 10 '09 at 14:57
  • 1
    Can you explain how wanting object orientation == artificially using C++? – Falaina Aug 10 '09 at 14:58
  • 4
    C++ does all this work *for you* so you can focus on actually coding, not doing it by hand. I know they are different languages, but C++ is superior in almost any way. You can do this (the answer) in C++ cleanly, and let the compiler take care of the details. So, why not learn C++ and use it rather than do a poor imitation of it. The only two reasons I can think of for not using C++ over C are compiler options (embedded systems) and existing code base. – GManNickG Aug 10 '09 at 15:04
  • I know that's probably gonna look like flame bait, but I still am baffled by the unwillingness by some people to learn C++ , who would rather "fake it". – GManNickG Aug 10 '09 at 15:05
  • 1
    GMan: This isn't a C vs C++ question, and arguing the relative merits here isn't useful. Take it elsewhere. – Paul Biggar Aug 10 '09 at 15:17
  • 3
    The OP notes that the students are mainly studying electronics and signal processing. So compiler options are very likely the reason for learning to do things like this in C. – John D. Aug 10 '09 at 15:20
  • 4
    Meh, comments are used for commenting. – GManNickG Aug 10 '09 at 15:20
  • It's useful to know how to do this because C++ doesn't handle callback-style code well (in my experience). One example is passing a C++ method pointer versus a static C function pointer plus an instance pointer. – Harvey Aug 10 '09 at 16:57
  • 13
    Not suitable for beginners! – Norman Ramsey Aug 10 '09 at 17:24
  • @GMan: +1 and Eiffel will even throw in built-in Design by Contract. – Daniel Daranas Aug 10 '09 at 17:34
  • This isn't run-of-the-mill desktop programming here, it's EE stuff; C++ is terrible/impossible for most signal processing/electronics applications – temp2290 Aug 10 '09 at 21:18
  • Because of lack of compilers? – GManNickG Aug 10 '09 at 23:09
  • The problem with introducing OO like this is that it's only a convention. You have to know all the conventions, not everyone on the project might be following them and if you're using macros to implement part of it you might turn up all sorts of weird bugs. This is why it's a good idea to have a compiler that sorts all that stuff for you. – wds Aug 12 '09 at 09:04
11

Portability -- rarely taught or mentioned in school, but comes up a lot in the real world.

azheglov
  • 5,475
  • 1
  • 22
  • 29
10

The (dangerous) side effects of macros.

freespace
  • 16,529
  • 4
  • 36
  • 58
  • so true, the pain still hurts!! – kenny Aug 12 '09 at 10:47
  • I think you should also mention the times when macros can greatly increase readability. Macros are very sharp, you can cut through problems or you can cut yourself. – Harvey Aug 12 '09 at 16:17
  • I was taught macros as the sharp tools to cut problems, I wasn't taught my head could come off tool :P Since the question is about the things you didn't learn, I don't think it fits. – freespace Aug 13 '09 at 10:40
9

Use valgrind

Draemon
  • 33,955
  • 16
  • 77
  • 104
9

Tools are important, so I'd recommend to at least mention something about

  • Makefiles and how the build process works
  • gdb
  • lint
  • the usefulness of compiler warnings

Concerning C, I think it's important to stress that the programmer should know what "undefined behaviour" really means, i.e. to know that there could be a future problem even if it seems to work with the current compiler/platform combination.

Edit: I forgot: teach them how to search and ask proper questions on SO!

groovingandi
  • 1,986
  • 14
  • 16
  • +1 for Makefiles. I can't believe I went through an entire undergraduate CS education without anyone ever teaching me how to use a damn makefile. I learned on my own, but plenty of my classmates didn't, and watching senior CS students compile larger programs by typing the commands manually every time (or worse, #including everything into one file) was infuriating. – Tyler McHenry Aug 11 '09 at 09:54
8

Use a consistent and readable coding style.

(This should help you in reviewing their code as well.)

Related: Don't prematurely optimize. Profile first to see where the bottleneck is.

Karl Voigtland
  • 7,637
  • 34
  • 29
  • Readability and maintainability. Basically, everything I learned by reading "Code Complete". But probably material is better left for the second or third semester programming class. In the first semester the students will have enough challenge just wrapping their heads around basic concepts. – Steve K Aug 10 '09 at 17:05
8

Know that when you increment a pointer, the new address depends upon the size of the data pointed to by that pointer... (IE, what's the difference between a char* being incremented and an unsigned long*)...

Knowing exactly what a segmentation fault really is first of all, and also how to deal with them.

Knowing how to use GDB is great. Knowing how to use valgrind is great.

Develop a C programming style... For example, I tend to write fairly object oriented code when I write large C programs (usually, all the functions in a particular .C file accept some (1) particular struct* and operate on it... I tend to have foo* foo_create() and foo_destroy(foo*) ctor's and dtors...)...

dicroce
  • 45,396
  • 28
  • 101
  • 140
  • "Develop a C programming style..." I'd go one step further and say you should document it informally. Sometimes, I forget my own style and break it. Having a quick document to refer to helps. The document could just be some fake code that has examples of style usage. If there's something missing, you just add it on the spot. – Harvey Aug 10 '09 at 17:09
  • First point should say "the new address depends upon __what the compiler thinks__ the size of the data pointed to...." If you have char* a; and int* b; and say a = (char*)b; ++a; ++b; assert(a == b); a may be pointing the same ints b is pointing to but the compiler thinks it pointing to chars. It doesn't know the "size of the data". It only knows the size of the supposed data. – jmucchiello Aug 10 '09 at 17:42
  • jmucchiello - That's why I worded like "size of the data pointed to by that pointer"... I was trying to get the point across that the size information is contained in the pointer type, NOT the thing being pointed at... – dicroce Oct 27 '09 at 20:57
8

Understanding the linker. Anyone using C should understand why "static int x;" at file scope does not create a global variable. The exercise of writing a simple program where every function is in its own translation unit and compiling each separately is not done often enough in the early stages of learning C.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
8

Always active warnings. With GCC, use at least -Wall -Wextra -Wstrict-prototypes -Wwrite-strings.

I/O is difficult. scanf() is evil. gets() should never be used.

When you print something which isn't '\n'-terminated, you have to flush stdout if you want to print it immediatly, e.g.

printf("Type something: ");
fflush(stdout);
getchar();

Use const pointers whenever possible. E.g. void foo(const char* p);.

Use size_t for storing sizes.

Litteral strings generally can't be modified, so make them const. E.g. const char* p = "whatever";.

Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95
6
  • Trashed memory can trigger all sorts of weird bugs.
  • Debuggers can lie to you.
JesperE
  • 63,317
  • 21
  • 138
  • 197
  • 3
    It would be helpful to say how debuggers can lie to you. For example, they initialize memory to zero, and change thread ordering (hiding race conditions in some cases). – Paul Biggar Aug 10 '09 at 15:13
  • Not only do they cause the system to behave differently -- I count that as normal behaviour for a debugger -- but they can have bugs in them which can cause symptoms like displaying incorrect information, or breakpoints failing to break when they are supposed to. – JesperE Aug 11 '09 at 07:03
5

I think that the overall idea seems really good. These are some extra stuff.

  1. A debugger is a good friend.
  2. Check the boundaries.
  3. Make sure that the pointer is actually pointing to something before it is used.
  4. Memory management.
Tobias Wärre
  • 793
  • 5
  • 11
5

Hope this wasn't posted before (just read through very quickly), but I think what is very important when you have to work with C, is to know about the machine representation of data. For example: IEEE 754 floating point numbers, big vs little endian, alignment of structs (here: Windows vs Linux)... To practice this, it is very useful to make some bit-puzzles (solving some problems without using a any functionality then printf to print the result, a limited number of variables and some logical operators). Also it is often useful to have a basic knowledge about how a linker works, how the whole compiling process works etc.. But especially understanding the linker (without that, it is so hard to find some kind of errors...)

The book which helped me most to improve my C and C++ skills was: http://www.amazon.com/Computer-Systems-Programmers-Randal-Bryant/dp/013034074X

I think that a deep knowledge about computer architecture makes the difference between a good and a bad C programmer (or at least it is a significant factor).

Markus Pilman
  • 3,104
  • 3
  • 22
  • 31
5

Teach them unit testing.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
5

How about general best practices?

  • Always assume that someone else has already written your code and that it is both freely available on the internet and better written and tested than anything you'll produce before your deadline.
  • Return early / Avoid else clauses
  • Initialize all variables
  • One page per function as a guideline (i.e. Use smaller pieces of code together)
  • When to use switch, if-else if, or a hash table
  • Avoid global variables
  • Always check your inputs and your outputs (I don't trust my own code.)
  • Most functions should return a status

    [ To others: feel free to edit this and add to the list ]

Regarding checking inputs:

I once wrote a big program in a hurry and I wrote all kinds of Guard Clauses, input checks, into my functions. When I ran the program for the first time, the errors from those clauses streamed by so fast I couldn't even read them, but the program did not crash and could be shut down cleanly. It was then a simple matter of going through the list and fixing bugs which went surprisingly fast.

Think of Guard Clauses as run-time compiler warnings and errors.

Harvey
  • 5,703
  • 1
  • 32
  • 41
  • to the first point: Reusing code is just part of how I work in a professional and personal context, but was not suitable at all for class (remember the whole deal with plagiarism in English class?). That is a vital programming practice, but is it suitable for students? – machinaut Aug 10 '09 at 20:11
  • arjay: When you're learning lists, you write your own list code. When you're getting paid, you find the free, well-tested, better implemented list code and use that. JoelFan: sure. Are you asking if there are hash tables in C? Or perhaps something else? – Harvey Aug 12 '09 at 16:11
5

I don't think you should be teaching tools. That should be left to Java teachers. They are useful and widely used but have nothing to do with C. A debugger is as much as they should hope to get access to. Many times all you get is printf and/or a blinking LED.

Teach them pointers but teach them well, telling them that they are an integer variable representing a position in memory(in most courses they also have some training in assembly even if it is for some imaginary machine so they should be able to understand that) and not an asterisk prefixed variable that somehow points to something and that sometimes becomes an array(C is not Java). Teach them that C arrays are just pointer + index.

Have them write programs that will overflow and segfault for sure and after that, make sure they understand why it happened.

The standard library is also C, have them use it and have their programs die painfully in your private tests because of having used gets() and strcpy() or double-freed something.

Force them to deal with variables of different type, endianness(Your tests could run in a different arch), float to int conversion. Make them use masks and bitwise operators.

i.e. teach them C.

What I got instead was some batch processing in C that could as well have been done in GW-BASIC.

4

This keyword in C: volatile ََََ

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Robert Deml
  • 12,390
  • 20
  • 65
  • 92
  • 6
    Seriously? How often has that come up for you doing basic stuff in C? – Jon Aug 10 '09 at 14:44
  • 1
    The one time I've needed it the compiler had a bug and ignored it! On the 16bit ISA bus on the PC you read a word by reading the same byte address twice. Which the compiler optimizes out, on the Zortech C++ compiler even if you add volatile. – Martin Beckett Aug 10 '09 at 14:51
  • @mgb: Blaming a compiler for your bug :) – Jon Aug 10 '09 at 14:57
  • wow, someone remembered Zortech! – azheglov Aug 10 '09 at 15:08
  • @Jon - There's a known bug with some compilers and the volatile keyword. See: http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf – John D. Aug 10 '09 at 15:23
  • I've only ever needed this when writing for a microcontroller, a DSP, or perhaps when using some low-level locking code where the lock variable couldn't be cached. – Harvey Aug 10 '09 at 17:01
  • 3
    For hardware-type programmers, volatile is very much needed. There are all kinds of hardware interfaces that demand that you access them in a very specific way. – Robert Deml Aug 10 '09 at 17:10
  • Or anyone who writes a multi-threaded program. The first time I learned about volatile was when I was also learning pthreads, and was thoroughly confused about a bug that was due to a (properly lock-protected) variable showing different values to different threads because it was not declared volatile and was being cached outside of memory. – Tyler McHenry Aug 11 '09 at 09:50
3
  1. Check the boundaries

  2. Check the boundaries,

    and of course,

  3. Check the boundaries.

And if you forgot one of these rules, use Valgrind. This applies to arrays, strings, and pointers, but it's really very easy to forget about what you're really doing when doing allocations and memory aritmethics.

Community
  • 1
  • 1
juanjux
  • 621
  • 6
  • 15
3
  • Where the language ends and the implementation begins: e.g., stdio.h is part of the standard library, conio.h is not, stuff like that;
  • The difference between undefined and implementation-defined behavior, and why things like x=x++ are undefined;
  • Just because it compiles doesn't mean it's right;
  • The difference between precedence and order of evaluation, and why a * b + c doesn't guarantee that a will be evaluated before b or c;
  • "It works on my machine" does not trump behavior specified by the language standard: e.g., just because void main() or x = x++ is giving you the results you expect for a specific platform and compiler doesn't mean it's okay to use;
  • Pretend you never heard of gets();
John Bode
  • 119,563
  • 19
  • 122
  • 198
3

Given their background, perhaps a good focus on C for embedded systems, including:

  • Static analysis tools (e.g. PC-Lint)
  • MISRA-C.
  • Exposure to multiple processors (e.g. PIC, STM32) and compilers
  • How to debug.
  • Real-time issues, including interrupts, debouncing signals, simple scheduling/RTOS.
  • Software design.

And very significantly: version control software. I work in industry and use it religiously, yet I'm astounded that it was never mentioned in the course of my degree!

Steve Melnikoff
  • 2,620
  • 1
  • 22
  • 24
  • I agree on the MISRA-C (possibly also the broader context of Safer-C) and relate it to the quality (safety, reliability, robustness). This awareness is so important... – Adriaan Aug 11 '09 at 11:24
2

The debugger is your friend. C is an easy language to mess up and the best way to understand your mistakes is often to see them under a debugger.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
2

It would be beneficial if the students were at some point exposed to tools that can help them write cleaner, better code. The tools may not all be relevant to them at this stage, but knowing what is available helps.

One should also stress the use of different (!) compilers with strict compiler warning flags and attending to each and every warning message.

albert
  • 8,285
  • 3
  • 19
  • 32
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
2

There are too many to name them all. Some of them are C specific; some of them are general best-practices kinds of things.

  • Learn to use the tools available
    • Revision control system. Every time it works, check it in.
    • Diff tools: diff, rdiff, meld, kdiff3, etc. Especially in conjunction with the RCS.
    • Compiler options. -Wextra -Wall __attribute__((aligned(8))), how to pack structs.
    • make: Produce debug and production versions
    • debugger: How to get and interpret a stack trace. How to set breakpoints. How to step through/over code.
    • Editor: Compile within the editor. Open multiple windows, M-x tags-query-replace (are my emacs roots showing?) etc.
    • cscope, kscope, [ce]tags, or other source browsing tools
  • Program defensively. assert(foo != NULL) in -DDEBUG; scrub user inputs.
  • Halt and Catch Fire when an error is detected. Debugging is easier when you core dump 2 lines after you detect the problem.
  • Maintain a 0-warning compile with -Wextra and -Wall enabled.
  • Don't put everything into 1 huge honking .c file.
  • Test. Test. And test some more. And check those tests in alongside your source. Because the instructor might come back and change the requirements after it's been turned in once.
user47559
  • 1,201
  • 8
  • 9
2

Go over the whole programming life cycle, including what happens to your code after you're done with it.

  • Pre-planning stages, and a bit on how to look for an existing project/existing code you can use to reduce the amount of original code
  • A small (Basic) overview of licenses and how that external code affects what licenses you can and can't use (and other considerations that go into licensing)
  • Concurrent version control, and versioning. I'd do SVN/Git, but to each his own. You will save them SO MUCH time if you introduce it to them now rather than learning on the job.
  • Show them what avenues there are for open-sourcing code (Google Code, Github, etc.) and when/how to tell if it's appropriate or not.

None of this is C-specific, but I add it because I personally just went through the 'C for Electrical Engineers' at my university, and this is all stuff I had to find out on my own.

machinaut
  • 495
  • 2
  • 4
  • 17
2

An important notion in C that I did not learn from my teachers is:

Operator * does not mean "pointer to" (on the left-hand side). It is instead the dereference operator - exactly as it is on the right-hand side (yes, I know it is disturbing to some).

Thus:

int *pInt

means that when pInt is dereferenced you get an int. Thus pInt is a pointer to int. Or put differently: *pInt is an int - dereferenced pInt is an int; pInt must then be a pointer to int (otherwise we would not get an int when it is dereferenced).

This means it is not necessary to learn more complicated declarations by heart:

const char *pChar

*pChar is of type const char. Thus pChar is a pointer to const char.


char *const pChar

*const pChar is of type char. Thus const pChar is a pointer to char (pChar itself is constant).


const char *const pChar

*const pChar is of type const char. Thus const pChar is a pointer to const char (pChar itself is constant).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

Indent Style. All teachers were saying that code must be indented but noone really gave directions on how to indent. I remember all students' code was really a mess.

  • I am quite strict with the indent style. Usually, I propose 2 different styles (K&R and Allman) for the indent and the bracks and ask them to choose (I prefer Allman for my students). Sometimes, I refuse the code if it's too messy – ThibThib Aug 11 '09 at 10:27
2
  • Non-procedural programming techniques including OOP patterns in C.
  • Advanced C preprocessor techniques
  • Debugging with something other than printf().
  • Complier and linker features, including building shared/dynamic objects.
  • Unit testing and mock objects, TDD in general.
cmcginty
  • 113,384
  • 42
  • 163
  • 163
1

Never believe the compiler. It is usually right that there is a problem, but except for the most trivial of errors, it's almost always wrong about what the problem is, and where it is.

NOTE: I didn't say ignore the compiler. I said don't BELIEVE it. It knows there is a problem, but it is frequently wrong about what exactly it is. Taking the compiler output at face value is a recipe for frustration and wasted time. Especially for complex errors.

Christopher
  • 8,815
  • 2
  • 32
  • 41
  • 4
    Nonsense. The compiler is your *friend*. Listen to what it says. – Kristof Provost Aug 10 '09 at 14:40
  • 2
    I'm with Kristof. I've wasted so much time in the past trying to fix errors when a carefull reading of the compiler output was what was really needed! – Jackson Aug 10 '09 at 15:04
  • 3
    The trick is learning how to interpret the compiler errors. It's usually wrong about what the error is, but it's CONSISTENT, so when it says the error is X on line Y, you can predict that it's really error Z on line Y-a... – Brian Postow Aug 10 '09 at 15:14
  • Implementing your own compiler helps a lot in understanding the nature of error messages. For example, why certain error messages only occur and some optimization levels. – JesperE Aug 11 '09 at 07:09
1

An array is a pointer The differences between the * (dereferencing) and & (addressof) operators and when to use both

And emphasize that the best (and really only) real place for C these days is in embedded systems and real-time apps where resources are scarce and run-time is a factor.

I didn't really appreciate C as a language until I took my embedded microprocessors systems class and we implemented the hardware via a reading through the programmer's guide in the manual for the Motorolla Dragonball board. Consequently, if it's at all possible (which may be hard, as you'll need to get cheap hardware) try to have them work on projects similar (implementing UART and interrupt vector tables, etc)...

Because although stuff like string processing, sorting, etc are toy classical school problems, they really aren't as useful anymore, and frustrate students who know there are easier ways. It's much more rewarding to & a byte with a bit-mask and watch an LED light up.

Oh, and I never learned about how to use stuff like gcc in school, or what was actually going on with makefiles. Pragmatic Programmers say that's a good thing to know.

moo
  • 11
  • 3
  • +1 for the "string processing that frustrates students" – ThibThib Aug 10 '09 at 16:55
  • 1
    An array is most definitely *not* a pointer; an array identifier will implicitly be converted to a pointer value in most contexts, but arrays and pointers are different animals entirely. – John Bode Aug 10 '09 at 17:18
  • Except at top level an array is NOT a pointer. Compare `extern char *p;` with `extern char buf[]`. – Norman Ramsey Aug 10 '09 at 17:26
1

Wrap all macro parameters in parentheses.

If a macro is a statement that is more complicated than an assignment or function call, wrap it thusly:

#define M(A) do { ... (A) ... } while (0)
Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
1

I used C89 in embedded programming and debugging the hardware was nightmarish. We had a few coding conventions that saved our sanities:

  1. All functions return a unique error code.
  2. All return values are auto variables passed by reference.

E.g.:

#define NOERR 0
#define VariableLookupNULL 1024
#define VariableLookupNOTFOUND 1025
... separate #define for each error
#define EvaluateExpressionNULL 1055
#define EvaluateExpressionUNKNOWNOP 1056


int EvaluateExpression( char *expression, int* result )
{
    ASSERT(result != 0);
    if (expression==0)
        return EvaluateExpressionNULL;

    *result = 0;
    while (*expression != 0)
    {
        switch (*expression)
        {
            case ' ':
            case '\t':
                break;  // ignore whitespace

            case 'a':
            ... other variables
            {
                int var = 0;
                int lookupResult = VariableLookup(*expression, &var);
                if (lookupResult != NOERR)
                    return lookupResult;

                *result += var;
                break;
            }

            ... check operators, et al.

            default:
                return EvaluateExpressionUNKNOWNOP;
        }

        ++expression;
    }

    return NOERR;
}

ASSERT was a debug macro that would abort the runtime.

Dour High Arch
  • 21,513
  • 29
  • 75
  • 90
1

Besides the obvious pointer stuff, I found nobody talking about commas when I was learning C.

a= 1, b= 2;

Sure you use it inside of for (;;) {} statements, but nobody ever understood why, and I've never seen anybody else use it outside of for statements.

But C treats commas differently from semi-colons. For example:

"if (a) b = a, c = a;"

is the same as

"if (a) { b = a; c= a; }"

and different than

"if (a) b = a; c = a;

Now, I'm not saying that the first form with commas is better, because it's going to trip up programmers that don't know better, and it's going to be hard to see if you use very small fonts, but there are times where you might run across this kind of code and its good to know what the language actually does.

Also, I found that if I have a lot of initialization at the top of a function,

a = 1,
b = 2,
i1 = 0,
i2 = 0,
i3 = 0,
i4 = 0,
dtmp = 0.0,
p = strtmp;

Having all these assignments be separated by a comma, makes them one statement, and let me "step" in the debugger past all of them in one step, instead of eight (or more). Yes, modern gui's make setting a breakpoint and skipping past less painful, but a single action (step) is still hard to beat.

woolstar
  • 5,063
  • 20
  • 31
1

My lecturers would occasionally talk about performance, but never made mention of the cost of branching compared with other operations, it wasn't until later when I studied microprocessors that I understood this. So many times we make unnecessary branches when the same problem can be solved with a bit of bitwise manipulation, finding the position of a letter in the alphabet for instance:

if (islower(letter)) {
   pos = letter - 'a' + 1;
} else if (isupper(letter)) {
   pos = letter - 'A' + 1;
}

vs:

pos = letter & 31;

of course, ascii was designed with this sort of thing in mind, so it's not as if showing us this would've been teaching us 'bad style' or some sort of 'magical hacks'... I now find myself using bitwise tricks every day to avoid branching.

-- my 2c worth

David Claridge
  • 6,159
  • 2
  • 27
  • 25
1

While not tied directly to C I would like to have learned about the technique of using ASSERTs to catch errors early (e.g. long before some bizarre error caused by overwriting of memory). Instead I independently discovered it some years later. This technique has catched many, many bugs (including some very subtle ones) that would otherwise have gone unnoticed.

In general an assert is added whereever some assumption can be made about a value in the program, e.g. it is never negative or zero or it is larger than some other variable.

E.g.:

assert(pInt)

if it is assumed pInt will point to reasonable data. Will fire for a null pointer. Often used for pointers passed to functions.

Or

assert(pInt < pMax)

where pMax points just past the end of an integer array that pInt is operating on.

Or

assert(yMass > 57.90)

(where yMass is the mass of single charged y-ion for a peptide)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1
  • No one ever taught me how to lay out a project. In a language like C, there are often header files, code files, libraries for static & dynamic linking, etc. What goes in the header, and what goes in the code file? Should these all just be stuck into a single directory, or should they be grouped in some way?
  • If they'll be using Visual Studio, it's to avoid ever learning how to use the compiler, and what the difference is between compiling and linking.
  • Teach them how to use a build tool like make, and also why.
davidtbernal
  • 13,434
  • 9
  • 44
  • 60
1

I wish my professors had taught us how to use the debugger. Instead I fumbled through instrumenting my code with printf's trying to figure out problems. Discovering gdb was like turning on a lightbulb. Being able to debug a crash using a core dump was especially helpful since a lot of newb C programming errors usually arise from bad pointer logic.

Nowadays unit testing would probably be a good practice to teach.

Ted Elliott
  • 3,415
  • 1
  • 27
  • 30
1
  • Using debuggers and other analysis tools (such as Valgrind etc.)
  • Optimization tricks, like Duff's device.

I'm very glad to say I was taught almost everything else that has been mentioned here (including unit testing and OOP patterns in C, really!).

Michael Foukarakis
  • 39,737
  • 6
  • 87
  • 123
1

#pragma directive, can be used to issue additional details to a processor. I worked on TI processors with C language, and this helped me a lot for defining the memory segments.

Also '__FILE__' & '__LINE__' predefined macros are very useful while debugging/logs, but I never knew this. These kind of thing should be told to students.

1

Integer promotions rules; representation of NULL pointers; alignment; sequence points; some kind of interesting optimisations the compiler is allowed to do; what is unspecified, undefined, and implementation defined -- and what it means. Good practices are also important, and its a shame some professional coding guidelines contains some really hugely stupid things. For example: do if (foo) free(foo); instead of free(foo); when foo can be NULL while the correct advice would precisely be the opposite: do free(foo) and never if (foo) free(foo); I'm also officially sick of shitty multi-threaded code so please either tell your students how to correctly write multi-threaded programs (by giving them a subset of known and provably safe techniques and forbidding them to use anything else or to invent something themselves) or warn them its just too complicated for them. Also tell them that buffer overflows are not acceptable in any context -- and neither are stack overflows ;)

Some things are not C specific at all but please also remind them what pre/post conditions are, loop invariants, complexity... Also some fundamental metrics used in serious industries are far too rarely known (for example cyclomatic complexity is absolutely crucial, yet up to now the only people I've met knowing about it have worked on safety critical software or have learned about cyclomatic complexity ultimately from people working on safety critical software)

Back to C: take a close look at the C99 standard: you will find tons of interesting subtilities rarely known by even otherwise good programmers. The worst is that when they take something for granting for a long time (and because of poor education this can even be things that are never been true or have not been true anymore for decades) and then have to face reality when their incorrect code introduce real life bugs and security holes, they shout on compilers and, instead of saying they are sorry for their incompetence, write long stupid rants insisting on why the behavior they falsely thought being used is the only one that make sense. Exemple: overflowing arithmetic on signed integers is often believed as being two's complement (at least if the computer is), when it is indeed not mandated and even false with GCC.

Before I forget: tell them to always compile with at least -Wall -Wextra -Werror (I tend to add -Wuninitialized -Winit-self -Wswitch-enum -Wstrict-aliasing -Wundef -Wshadow -Wpointer-arith -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wold-style-definition -Wredundant-decls)

xilun
  • 631
  • 4
  • 2
1

One thing I'd like to see taught by more programming professors is a little about source control. A day on any VCS: why you use it, some simple operations, version numbering, etc.

There are far too many graduates that find source control a foreign concept...it doesn't matter that they're EE's or CS majors, if they're writing code, they should know a little about version control systems.

KFro
  • 764
  • 3
  • 8
0

Hygienic names in C macros:

#define SOME_MACRO(_x) do {     \
  int *x = (_x);                \
  ...                           \
} while(0)

Defining x this way inside a macro is dangerous, because (_x) may also expand to x, ending up with:

do {
  int *x = x;
  ...
} while(0)

which might not get any warning from your compiler, but actually initialize your x pointer with garbage (rather than the shadowed x from the outer scope).

Its important to use names that you know are unique to that macro. The C preprocessor has no mechanism to automate this, so you just have to choose ugly names for your macro-defined variables, or just avoid them for these purposes.

Peaker
  • 2,354
  • 1
  • 14
  • 19
0

The compiler is not always right. Particularly when developing for embedded systems.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
0

How to load tape into tape player - I am not joking, I have learned C on ZX Spectrum and every compilation required loading a compiler from a tape.

Those were times :D

zbychuk
  • 71
  • 1
  • 1
0

Good portable coding concepts, programming models (e.g. ILP32 vs. LP64), and expose them to different C compilers and toolchains (not all the world uses GCC).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
fpmurphy
  • 2,464
  • 1
  • 18
  • 22
0

The concepts of order of execution and sequence points are pretty useful, and not much discussed.

Knowing that x=x++; invokes undefined behavior is useful. Knowing why it oes can be much more educational.

Given your audience, some discussion of "volatile" might be useful, as well as other concepts in interfacing with hardware. How to handle write-only registers, that sort of thing.

Mark Bessey
  • 19,598
  • 4
  • 47
  • 69
0

Initialise pointers, that would otherwise be undefined, to a value that will make the program crash immediately when dereferenced (instead of overwriting of memory in some arbitrary location).

This will work as intended on most 32 bit system:

int *pInt = (int *)0xDEADBEEF

I am not sure what would be a good value on a 64 bit system.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

That a pointer is nothing but a datatype for storing addresses, just as an int is a datatype for storing integers. When I assimilated this, everything about pointers and pointer-arithmetic just fell into place.

JesperE
  • 63,317
  • 21
  • 138
  • 197
0

Simple debug tool, printf(). If you don't have any debug tools!!

Sachin
  • 26
  • 1
  • 4
0

Simulate objects with structures and function pointers

JohnIdol
  • 48,899
  • 61
  • 158
  • 242
0

Alone semicolon is a NOP operation:

if(cond0) { /*...*/ }
else if(cond1) ;  //is correct and does nothing 
else { /*...*/} 

Comma operator:

a = (++i, k);  //eq: ++i; a = k;
saxi
  • 409
  • 4
  • 9
0
if(constant=variable)
{
work();
}
Mandrake
  • 363
  • 1
  • 3
  • 11