views:

2399

answers:

56

Hi

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 2nd 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, ...) ?

+3  A: 

keyword: volatile

Robert
Seriously? How often has that come up for you doing basic stuff in C?
Jon
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
@mgb: Blaming a compiler for your bug :)
Jon
wow, someone remembered Zortech!
azheglov
@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.
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
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
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
+6  A: 
  • Trashed memory can trigger all sorts of weird bugs.
  • Debuggers can lie to you.
JesperE
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
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
+13  A: 

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
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
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
+1 for C is not C++
Michał Piaskowski
+1  A: 

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
Nonsense. The compiler is your *friend*. Listen to what it says.
Kristof Provost
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
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
@Kristof: See my edit.
Christopher
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
+5  A: 

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
+12  A: 

unsigned vs signed.

Bit shift operators

Bit masking

Bit setting

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

Robert
+1 for bitshifting - also, bitmasking.
Meredith L. Patterson
@Meredith - good additions. Thanks.
Robert
+1 my interview question for C-types is to write a single function to count the number of 1s in an integer of various sizes. Yeah, I know not strictly C.
kenny
+1 for bit shifts...too many people dont realize that (x>>3) == (x/8).
KFro
+9  A: 

The (dangerous) side effects of macros.

freespace
so true, the pain still hurts!!
kenny
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
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
+9  A: 

Use valgrind

Draemon
+8  A: 

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
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
+10  A: 

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
+1 for noting that you can do OO without explicit support for it in your language.
Jon
I still remain confused why you'd artificially use C++.
GMan
Can you explain how wanting object orientation == artificially using C++?
Falaina
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.
GMan
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".
GMan
GMan: This isn't a C vs C++ question, and arguing the relative merits here isn't useful. Take it elsewhere.
Paul Biggar
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.
Meh, comments are used for commenting.
GMan
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
Not suitable for beginners!
Norman Ramsey
@GMan: +1 and Eiffel will even throw in built-in Design by Contract.
Daniel Daranas
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
Because of lack of compilers?
GMan
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
+30  A: 

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
Matthieu
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
@Blaenk : You are right, I was a bit lazy, that's done !
Matthieu
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
Don't forgetchar const* pChar // pointer to a CONSTANT char - same as const char* pCharchar const* const pChar // Both
Stephen Nutt
+2  A: 

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
+29  A: 

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.

ph0enix
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
@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
Same thing with me. I did not understand pointer until I learned assembler...
del-boy
@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
1[c] is also equivalent to *(c + 1) http://codepad.org/kGapFZaG
Liran Orevi
@Liran, you are evil! :)
kenny
Harvey
+17  A: 

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
+1  A: 

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

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.

lsc
+2  A: 
  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.

juanjux
A: 

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

bdonlan
+10  A: 

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

azheglov
+8  A: 

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
"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
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
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
+7  A: 

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
+5  A: 

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
+4  A: 

Teach them unit testing.

Bryan Oakley
+8  A: 

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 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
+1  A: 

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
+1 for the "string processing that frustrates students"
ThibThib
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
Except at top level an array is NOT a pointer. Compare `extern char *p;` with `extern char buf[]`.
Norman Ramsey
+7  A: 

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
Why not include -Werror ? Else there is a temptation to neglect the warnings.
Andrew Y
More generically: teaching compiler flags and why they make such a difference.
lilbyrdie
I agree with Andrew Y, always use -Werror.
JesperE
+5  A: 

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
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?
ajray
hash tables in C?
JoelFan
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
A: 

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

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

fpmurphy
+2  A: 
  • 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
+1  A: 

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
+2  A: 

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
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
+1  A: 

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
Didn't you have a real debugger?
JesperE
+2  A: 

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.
+1  A: 

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 its going to trip up programmers that don't know better, and its 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 seperated by a comma, makes them one statement, and lets me "step" in the debugger past all of them in one step, instead of eight ( or more ).

woolstar
alternatively, you could just put a breakpoint after the initialization. Even better: do both.
ajray
A: 

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
+1  A: 

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.

ajray
+4  A: 

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.

jbcreix
A: 

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
+2  A: 

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
"*pInt is an int", "pInt is a pointer to int"-> this is already what I teach it
ThibThib
+1  A: 

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

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
This was just a question the other day ... 0xBADC0FFEE0DDF00D
Casey
What's wrong with 0?
Ryan Fox
There's nothing wrong with NULL, people just think values that spell something are cool.
Mark Bessey
A: 
  • 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.
notJim
+1  A: 

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

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

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

Sachin
+2  A: 

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.

ThibThib
+1  A: 
  • 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.
Casey
+1  A: 
  • 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
A: 

the most important tip for a beginner Student is

Sintax in C is case Sensitive

RRUZ
+1  A: 

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

Ritul
A: 

Simulate objects with structures and function pointers

JohnIdol
A: 

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

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

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
A: 
if(constant=variable)
{
work();
}
Arabcoder