C Programming classes

I taught an Introduction to C Programming class for the first time April 26 disguised in the Embedded Workshop series so that I do not get swamped. It apparently went well as I have been asked to do it again as well as a second more advanced one.

This is the slide deck from the first class: https://calendar.dallasmakerspace.org/webroot/files/Files/file/Introduction%20to%20C%20Programming.ppt

It will be better the second time around because I left out some things the first time. I was afraid that was too much material to fit within two hours when it only took an hour and a half.

Comments? What to cover in the second class?

3 Likes

Well…since nobody has bit yet, I’ll byte :wink:
Passing variables to/from functions
Global versus local
Scope
Printf formatting
Other “stuff” that makes OOP such a joy…
/OOP

1 Like

Assuming C so no real OO unless you count structs with function pointers, and that is a BEATDOWN.

2 Likes

Let’s just say that C and I don’t get along well…yet.
Being self taught in assembler probably didn’t help the cause.
Seeing the potential and having projects provides the incentive/motivation to learn.

1 Like

Sounds like you have a good grasp though.

I think the differentiators for C would be:

Pointers / dereferencing – covered already
Memory management, malloc / free (not sure how applicable these are to arduino)
Structs / Typedefs – covered already

1 Like

Depends upon the Arduino. There is more then the UNO, and several of the boards have more memory and power then the computers many of us learned on in the seventies and eighties.

2 Likes

If you get around to covering memory management in C, I suggest teaching people how to use these functions from an old Dr Dobbs magazine (Aug 1990, Debugging Memory Allocation Errors) article on ‘wrapping’ the memory management functions in order to debug mistakes. These little routines have saved me countless hours debugging both my own code and others…


/* memMalloc() -- Same as malloc(), but registers activity using memTrack().
* Copyright (c) 1990, Cornerstone Systems Group, Inc. 
*/

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

#include <mem.h>

void *memMalloc(size_t bytes, char *tag)
{
   void *allocated;
   allocated = malloc(bytes);
   memTrack_alloc(allocated, tag);
   return(allocated);
}

/* memFree() -- Same as free(), but registers activity using memTrack().
*  Copyright (c) 1990, Cornerstone Systems Group, Inc. 
*/

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

#include <mem.h>

void memFree(void *to_free, char *tag)
{
   if (memTrack_free(to_free, tag))
      {
      free(to_free);
      }
}
/* MEMTRACK.C -- Module to track memory allocations and frees that occur 
* in the other mem...() routines. Global routines:
*     memTrack_alloc() -- Records allocations.
*     memTrack_free()  -- Records attempts to free. 
*  Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/

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

#include <mem.h>

static FILE  *memTrack_fp(void);
static void   memTrack_msg(char *msg);

#define  ALLOC   'A'
#define  FREE    'F'

/* Track an allocation. Write it in the debugging file in the format 
*   A 0000:0000 tag */
void memTrack_alloc(void *allocated, char *tag)
{
   FILE  *fp;

   if (fp = memTrack_fp())
      {
      fseek(fp,0L,SEEK_END);
      fprintf(fp,"%c %p %s\n",ALLOC, allocated, tag);
      fclose(fp);
      }
}

/*  Track freeing of pointer. Return FALSE if was not allocated, but tracking 
 *  file exists. Return TRUE otherwise. */
int memTrack_free(void *to_free, char *tag)
{
   int   rc = 1;
   FILE  *fp;
   void  *addr_in_file = 0;
#define  MAX_LTH  200
   char  line[MAX_LTH];
   char  found = 0;
   char  dummy;
   int   ii;
   long  loc;
   if (fp = memTrack_fp())
      {
      rewind(fp);
      for ( loc=0L; fgets(line,MAX_LTH,fp); loc = ftell(fp) )
         {
         if (line[0] != ALLOC)         /* Is the line an 'Allocated' line?  */
            continue;                  /*   If not, back to top of loop.    */
         ii = sscanf(line,"%c %p",&dummy, &addr_in_file);
         if (ii==0 || ii==EOF)
            continue;
                                       /* Is addr in file the one we want?  */
         if ( (char *)addr_in_file - (char *)to_free == 0 )
            {
            found = 1;                 
            fseek(fp,loc,SEEK_SET);    /* Back to start of line    */
            fputc(FREE,fp);            /* Over-write the ALLOC tag */
            break;
            }
         }
      fclose(fp);
      if (!found)
         {
         char  msg[80];
         sprintf(msg,"Tried to free %p (%s).  Not allocated.",to_free,tag);
         memTrack_msg(msg);
         }
      }
   return(rc);
}

/* Return FILE pointer for tracking file.  */
static FILE  *memTrack_fp()
{
   static char  *ep = NULL;    /* Points to environment var that names file */
   FILE  *fp = NULL;           /* File pointer to return                    */

   if (ep == NULL              /* First time through, just create blank file */
   &&   (ep = getenv("MEMTRACK"))
   &&   (fp = fopen(ep,"w")) )
      {
      fclose(fp);
      fp = 0;
      }
   if (ep)                     /* If we have a file name, proceed.          */
      {                        /*   Otherwise, do nothing.                  */
      fp = fopen(ep,"r+");     /* Open debugging file for append access.    */
      if (!fp)
         {
         fprintf(stderr,"\a\nCannot open %s\n\a",ep);
         }
      }
   return(fp);
}

/* Write a message to the debugging file. */
static void memTrack_msg(char *msg)
{
   FILE  *fp;

   if (fp = memTrack_fp())
      {
      fseek(fp,0L,SEEK_END);
      fprintf(fp,"\n%s\n",msg);
      fclose(fp);
      }
   else
      {
      fprintf(stderr,"%s\n",msg);
      }
}

/* memCalloc() -- Same as calloc(), but registers activity using memTrack().
*  Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <mem.h>

void *memCalloc(size_t num_elems, size_t bytes_per_elem, char *tag)
{
   void *allocated;
   allocated = calloc(num_elems, bytes_per_elem);
   memTrack_alloc(allocated, tag);
   return(allocated);
}

/* memRealloc() - Same as realloc(), but registers activity with memTrack().
*  Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <mem.h>

void *memRealloc(void *allocated, size_t bytes, char *tag)
{
   memTrack_free(allocated, tag);
   allocated = realloc(allocated, bytes);
   if (allocated)
      {
      memTrack_alloc(allocated, tag);
      }
   return(allocated);
}

/* memStrdup() -- Same as strdup(), but registers activity using memTrack().
*  Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <mem.h>

void *memStrdup(void *string, char *tag)
{
   void *allocated;
   allocated = strdup(string);
   memTrack_alloc(allocated, tag);
   return(allocated);
}


/* MEM.H  -- ** Copyright (c) 1990, Cornerstone Systems Group, Inc. */

#ifdef MEMTRACK

void *memCalloc(size_t num_elems, size_t bytes_per_elem, char *tag);
void  memFree(void *vp, char *tag);
void *memMalloc(size_t bytes, char *tag);
void *memRealloc(void *oldloc, size_t newbytes, char *tag);
void *memStrdup(void *string, char *tag);
     /* The next two functions are only called by the other mem functions   */
void memTrack_alloc(void *vp, char *tag);
int  memTrack_free(void *vp, char *tag);
#else
#define  memCalloc(NUM,BYTES_EACH,TAG)       calloc(NUM,BYTES_EACH)
#define  memFree(POINTER,TAG)                free(POINTER)
#define  memMalloc(BYTES,TAG)                malloc(BYTES)
#define  memRealloc(OLD_POINTER,BYTES,TAG)   realloc(OLD_POINTER,BYTES)
#define  memStrdup(STRING, TAG)              strdup(STRING)
#endif



/* DEMOHEAP.C - Demonstrate use of heap...() functions.
*  Copyright (c) 1990 - Cornerstone Systems Group, Inc.
*/

#include <stdio.h>
#include <malloc.h>
#include <heap.h>

static void my_own_msg_func(char *msg);

main()
{
   char *allocated_but_never_freed;
   char *this_one_is_ok;
   char *freed_but_never_allocated;
   heapPrt_set_msg_func(my_own_msg_func);
   allocated_but_never_freed = malloc(10);
   heapPrt("after first malloc()");
   this_one_is_ok            = malloc(20);
   heapPrt("after second malloc()");
   free(this_one_is_ok);
   heapPrt("after first free()");
   free(freed_but_never_allocated);
   heapPrt("after second free()");
   return(0);
}

/* heapPrt() makes its report with puts() by default.  This will not be
*  appropriate for some applications, so we will demonstrate the use of an
*  alternative message function.  This one writes to stderr.
*  The alternative function should take one argument (a char *).  Its 
*  return value is ignored, so it might as well be void.
*/
static void my_own_msg_func(char *msg)
{
   fprintf(stderr,"My own message function: %s\n",msg);
}
OUTPUT:
My own message function:     1 allocations,     10 bytes, after first malloc()
My own message function:     2 allocations,     30 bytes, after second malloc()
My own message function:     1 allocations,     10 bytes, after first free()
My own message function:     1 allocations,     10 bytes, after second free()


/* heap.h - Header file for use with heap...() functions.
*   Copyright (c) 1990 - Cornerstone Systems Group, Inc.
*/

void heapPrt(char *tag);
void heapPrt_set_msg_func(void (*new_msg_func)() );
void heapUsed(unsigned int *numused, long *totbytes);

/* HEAPUSED.C -- Tell how much of heap has been used. For use with MS C 5.x
*  Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/
#include <malloc.h>
#include <heap.h>

void heapUsed(
unsigned int   *numused,
long           *totbytes)
{
   struct _heapinfo hinfo;
   int    status;
   *numused  = 0;
   *totbytes = 0L;
   hinfo._pentry = (char *)0;
   while ( (status=_heapwalk(&hinfo)) == _HEAPOK)
      {
      if (hinfo._useflag == _USEDENTRY)
         {
         ++ (*numused);
         *totbytes += hinfo._size;
         }
      }
}

/* HEAPPRT.C -- Print summary information about heap. For use with MS C 5.x
*   This module contains two functions:
*     heapPrt() prints the summary information.
*     heapPrt_set_msg_func() allows you to specify a function for heapPrt()
*        to use, other than printf().
*   Copyright (c) 1990, Cornerstone Systems Group, Inc.
*/
#include <stdio.h>
#include <malloc.h>
#include <heap.h>
static void (*heapPrt_msg_func)() = 0;

/*--------------------------------------------------------------------------*/
void heapPrt(
char  *tag)        /* Description of where you are in processing            */
{
   unsigned int   numused;          /* Number of allocations used           */
   long           totbytes;         /* Total bytes allocated                */
   char           msg[80];          /* Message to display                   */
   heapUsed(&numused, &totbytes);
   if (!heapPrt_msg_func)
      heapPrt_msg_func = puts;
   sprintf(msg, "%5u allocations, %6ld bytes, %s",numused,totbytes,tag);
   heapPrt_msg_func(msg);
}
/*--------------------------------------------------------------------------*/
void heapPrt_set_msg_func(
void (*new_msg_func)())
{
   heapPrt_msg_func = new_msg_func;
}
/*--------------------------------------------------------------------------*/
3 Likes

I wouldn’t mind seeing some more info on the preprocessor and compiler control. Now that I have multiple platforms, I’m thinking I maybe able to use some of this to perhaps prevent the need for near duplicate source files for the different target platforms.

I, also, wouldn’t mind seeing instances where using C++ makes more sense than C in the microcontroller environment.

1 Like

Gak! Don’t use dynamic memory with an embedded system. Thinking about it, just never use it… :slight_smile:

Should you need something like this, try https://en.wikipedia.org/wiki/Electric_Fence

1 Like

Why not? Today’s ‘embedded’ systems have more memory available the most of the computers I used for the first decade of my programming life… Granted that dynamic memory is a major source of bugs, but there are times when it is appropriate. That Electric Fence looks like a more refined tool versus the early code from Dr. Dobbs… but I have enjoyed today’s stroll down memory lane reading some old Dr. Dobbs articles.

1 Like

The Arduino makes frequent use of the C++ extensions to the C language, most often with their libraries… It is nice to have the functionality for a hardware module embedded in a class.

1 Like

Exactly … dynamic memory allocation is a major source of bugs. Nearly any(1) problem can be solved without the use of heap-based dynamic allocation (malloc et al.), and that goes for another bug-inducing coding method: recursion. Time has proven that programmers can’t be trusted to Do The Right Thing with these concepts; whole languages have been invented to try to fix that (and end up having different problems which are just as fatal).

Safety-to-life system programming standards are near-universal in their prohibition against dynamic memory.


(1) Not gonna say all, because I don’t stick my neck out like that.

2 Likes

I agree completely if your producing something for production; however, most of our members, particularly those interested in Bill’s class are learning and using for hobby projects. Perfect places and situations to experiment with things like dynamic memory and to learning debugging :smiling_imp:

1 Like

In the same vein, something else might be when NOT to use C++. Is there a case by using a hardware class that the peripheral can no longer be used at a maximum rate? I guess the same question could extend to C: is there a case where C is unable to deliver the hardware performance of machine code?

For all of the above it’s likely easier these days to just purchase a faster microprocessor.

Not really sure if this is the type of thing that Bill would want to cover. I find the topic rather interesting despite the “faster microprocessor” solution.

1 Like

Not so much a C++/C issue (C++ is as fast as C usually), but nearly ALL of the Arduino specific language features but most especially the DigitalRead DigitalWrite functions are MUCH slower then more direct chip specific access methods. If you need speed you really ought to be going lower level (not nescessarilly assembly language) and use direct port methods with bit operations. However, another route is to simply throw more power at the problem. For instance use the 32 bit ARM M3 DUE instead of the 8 bit AVR ATMEGA 328 to toggle that output pin…

The latter is the approved Micro$oft/Intel approach…

1 Like

The general guideline is not to use C++ exceptions because it takes up too many resources. Templates tend to generate a lot of code as well, so good to stay away from if you’re working in 10s of KiB. I’m sure there are a few others as well, but most people just use it as a nice way to create some containment for related functions and data and step out of a sloppy pseudo-OOP structure.

3 Likes

Regarding the other discussion about allocating and deallocating heap, if you’re not careful with allocating and deallocating and your memory management algorithm, memory fragmentation can be a long-term issue that will come up with embedded when it never would on a PC.

It’s such a dicey issue that many recommend if you need to alloc and dealloc a certain object (for IO data packets or something), you set up a memory pool just for that use, leaving main heap as statically allocated as possible.

I am no expert on certifiably safe software, but dynamic allocation is a tough one to prove as safe.

2 Likes

Great discussion, guys, keep it coming…

The version of those functions Paul Stoffregen developed for Teensyduino / AVR optimize to the minimal possible set of machine instructions to be compatible with the existing Arduino core. For non-PWM pins that is one or two machine instructions. For PWM pins there are a few more instructions to manipulate the timer.

I did something similar for Tiny Core: I eliminated one (or two?) cases that only gave marginal improvement but added an improved lookup.

I have no idea if he has done the same for his SAM / ARM boards.

2 Likes

Newer compilers implement long long.

May 2000. It is safe to assume long long is available.

For Real, the number of decimals (e.g. 6 to 7 for float; 15 to 16 for double) might be good to include.

For avr-gcc (Arduino) double is an alias for float.

Typedef - Allows assigning a simple name to a complicated type.

The type does not have to be complicated. For example…

Custom types (typedef) are a nice way to insulate code from platform. The C++ standard has a long list of them (e.g. uint8_t, int32_t, etc.).

Typed constants (const) should be favoured over the equivalent macros (#define).

Nick has collected a great list of traps from his experience on the Arduino forum…
http://forum.arduino.cc/index.php?topic=190272.0

In the case of Arduino (AVR) exception support is non-existent.

2 Likes