Runtime Environment

Command Line Arguments

Usually, we see main prototyped as:

int main(void);
However, main is sort of overloaded to take parameters as well:
   /* These prototypes are the same */ 	
int main(int argc, char *argv[]);
int main(int argc, char **argv);
As we've seen with arrays as parameters, the declarations above are equivalent. This trivial program simply prints out each argument:
int main(int argc, char *argv[])
{
  int i;
  
  for (i = 0; i < argc; i++)
    printf("arg%i = %s\n", i, argv[i]);
    
  return 0;
}
If our program was named foo.exe and we were to invoke the program like this:
foo one two three 911
we would see something like this printed out:
foo
one
two
three
911
Another example:
foo one "two three" four 911
foo
one
two three
four
911
Within an IDE like Visual Studio we might see:
E:\Data\Courses\Code\Chapter16\RTL\Debug\foo.exe
one
two three
four
911
because the IDE invokes the program using the entire path to the executable.

Another way of printing the arguments using pointers instead of subscripts:

int main(int argc, char **argv)
{
  while (*argv)
    printf("%s\n", *argv++);

  return 0;
}

Quick check: Complete the table below. (Assume a 64-bit computer)

Expression    Type     sizeof(Expression)
-----------------------------------------
  argc         int          4 
  argv
 *argv
**argv

Diagram of the arguments when invoked as:
foo one "two three" four 911


Note: Because argv is an array of pointers to characters (strings), you can only pass strings to a program. If you want to use the parameter as a number, you will have to convert it from a string to a number yourself. See the Data Conversion section in the C Runtime Library, specifically the atoi function.

Imagine having to deal with hundreds of options like the gcc command options. This summary of options shows hundreds of options alone. And don't believe for a minute that they are only for command-line apps. All "real" games have hundreds of them:

Environment

There's actually another prototype for main which looks like this:

int main(int argc, char **argv, char **env);
where env is similar to argv in that it is a pointer to a pointer to a char. These pointers point to the strings in the environment.
int main(int argc, char **argv, char **env)
{
  while (*env)
    printf("%s\n", *env++);

  return 0;
}
The output from the above program running on a Windows computer looks like this. Notice that this program produces the same results as the SET command typed at the console:
C:\> set     type 'set' and press return
We can experiment with this in C:
#include <stdio.h>  /* printf         */
#include <string.h> /* strchr, strcmp */

char *GetEnvironmentSetting(char **env, const char *key)
{
    /* Strings are of the form: key=value */ 
  while (*env)
  {
      /* Find the equal sign so we can terminate the key */
    char *equal = strchr(*env, '=');

      /* This should ALWAYS be non-NULL */
    if (equal)
    {
      *equal = 0;
      if (!strcmp(key, *env))  /* If key matches the current variable */ 
        return equal + 1;      /*   The value is 1 after the '='      */ 
    }

    env++; /* Next key */
  }

  return NULL;  /* Not found */ 
}

int main(int argc, char **argv, char **env)
{
  char *key;   /* The environment variable  */
  char *value; /* The value of the variable */

    /* Must provide a key on the command line */
  if (argc < 2)
  {
    printf("Must provide an environment string.\n");
    return 1;
  }

  key = argv[1];

    /* Get value from key/value pair */ 
  value = GetEnvironmentSetting(env, key);

    /* Was the string in the environment? */
  if (value)
    printf("%s is %s.\n", key, value);
  else
    printf("%s is not set.\n", key);

  return 0;
}
The environment is a very powerful and easy way to seamlessly configure software. You should try exploiting this concept.

Final notes on main. Not all implementations will support the environment pointer:

ANSI C:

int main(void);                       
int main(int argc, char **argv);      
Traditionally accepted implementations:
int main(void);
int main(int argc, char **argv);
int main(int argc, char **argv, char **env);                                      
extern char *environ[];   /* External global variable */ 
Finally, there is a getenv function (stdlib.h) that you can call anywhere in your program that will return the value a specific environment string:
char *getenv(char const *string);
This is almost identical to the GetEnvironmentSetting function implemented above and can be used without the parameter to main:
#include <stdio.h>  /* printf */
#include <stdlib.h> /* getenv */

int main(int argc, char **argv)
{
  char *key;   /* The environment variable  */
  char *value; /* The value of the variable */

    /* Must provide a key on the command line */
  if (argc < 2)
  {
    printf("Must provide an environment string.\n");
    return 1;
  }

  key = argv[1];

    /* Get value from key/value pair */ 
  value = getenv(key);

    /* Was the string in the environment? */
  if (value)
    printf("%s is %s.\n", key, value);
  else
    printf("%s is not set.\n", key);

  return 0;
}

Custom exit Code

You can specify which function to call when your program terminates by using the atexit function. You need to include the stdlib.h header.

int atexit( void(*func)(void) );
What is the type of func? What is the type of atexit?

This example simply returns from main, which causes the program to terminate:

void MyExit(void)
{
  printf("In MyExit function...\n");
}

int main(void)
{
  atexit(MyExit);
  printf("In main...\n");

  return 0;
}
Output:
In main...
In MyExit function...
This code actually calls exit to terminate the program prematurely (but safely):

void MyExit(void)
{
  printf("In MyExit function...\n");
}

void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  exit(0);
}

int main(void)
{
  atexit(MyExit);
  printf("In main...\n");
  SomeFn();

  return 0;
}
Output:
In main...
In SomeFn calling exit...
In MyExit function...
This program sets up several exit functions:

void MyExit1(void)
{
  printf("In MyExit1 function...\n");
}

void MyExit2(void)
{
  printf("In MyExit2 function...\n");
}

void MyExit3(void)
{
  printf("In MyExit3 function...\n");
}

void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  exit(0);
}

int main(void)
{
  atexit(MyExit1);
  atexit(MyExit2);
  atexit(MyExit3);
  printf("In main...\n");

  return 0;
}

And we can see that they are called in the reverse order in which they were assigned:

Output:

In main...
In MyExit3 function...
In MyExit2 function...
In MyExit1 function...

Note: The registered exit functions will only be called if the program terminates normally. If the program terminates abnormally (e.g. by calling abort, _exit, or performing some illegal operation like division by 0 or illegal memory access) the functions will not be called.

So, changing this:
void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  exit(0);
}
to this:
void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  abort();
}
Will prevent the exit functions from being called.

Executing Programs from C Code

Suppose you have an executable that you want to use within your own program. How can you call it? The easiest way is with the system function from stdlib.h:

int system(const char *command);
Using it in a program like this:
int main(void)
{
  system("notepad");
  return 0;
}
will cause the Windows Notepad application to run. You can also pass command line arguments to other programs as well. The example below calls the GNU gcc compiler and passes a filename to it to compile:
int main(void)
{
  system("gcc main.c");
  return 0;
}
A simple program to execute almost any command from C:
int main(void)
{
  char buffer[100];
  
  printf("What command do you want to execute?\n");
  fgets(buffer, 100, stdin);
  system(buffer);
  printf("This line will be printed after the system call above terminates.\n");

  return 0;
}
Example run in Windows: (program is rtl.exe):
E:\Data\Courses\Code\Chapter16\RTL\Debug>rtl
What command do you want to execute?
dir d:\ e:\
Volume in drive D is Applications
 Volume Serial Number is 1084-18F9

 Directory of d:\

04/10/2017  07:03a      <DIR>          borlandc
05/24/2017  11:22a      <DIR>          CygWin
12/06/2016  06:08p      <DIR>          Data
12/10/2016  05:56p             126,875 dirinfo.mcd
12/07/2016  03:37p      <DIR>          JBuilder4
07/19/2017  12:41p      <DIR>          Program Files
07/05/2017  10:25a      <DIR>          stuff
               1 File(s)        126,875 bytes
               6 Dir(s)     604,979,200 bytes free

 Volume in drive E is Data
 Volume Serial Number is DCA8-DC74

 Directory of e:\

05/10/2017  01:59p      <DIR>          CVSRoot
07/19/2017  07:54a      <DIR>          Data
01/15/2017  09:49a              16,663 dirinfo.mcd
12/07/2016  12:36p      <DIR>          Documents and Settings
07/22/2017  10:06a      <DIR>          Download
02/05/2017  10:46a              29,607 Export_HKEY_CURRENT_USER_Software_Microsoft_DevStudio.reg
12/06/2016  02:51p      <DIR>          i386
07/15/2017  08:59a      <DIR>          images
02/01/2017  09:06a      <DIR>          Installs
12/07/2016  12:29p      <DIR>          Program Files
05/23/2017  12:44p                   0 s1cc
05/23/2017  12:44p                   0 s1cc.1
05/23/2017  12:44p                   0 s1cc.2
05/23/2017  12:44p                   0 s1cc.3
05/23/2017  12:44p                   0 s1cc.4
02/01/2017  11:58a      <DIR>          Share
02/05/2017  10:39a      <DIR>          sp5
07/05/2017  04:10p      <DIR>          stuff
07/17/2017  06:59a      <DIR>          temp
03/01/2017  05:02p              25,761 TempMon1.gif
04/23/2017  08:43a      <DIR>          WebReaper
12/07/2016  12:39p      <DIR>          WINNTX
               8 File(s)         72,031 bytes
              14 Dir(s)   4,069,343,232 bytes free


This line will be printed after the system call above terminates.
Notes:

Using exec and Others

The exec family of functions is similar to the system function, but has more capabilities.

Passing a (variable-length) list to exec under Windows:
#include <stdio.h>  /* printf, perror */
#include <unistd.h> /* exec functions */
#include <errno.h>  /* errno          */

int main(void)
{
  int retval;

    /* Using a list only (must specify path) */
  retval = execl("c:\\Windows\\system32\\notepad.exe", "c:\\Windows\\system32\\notepad.exe", "c:\\eula.1040.txt", NULL);

    /* We only reach this if the call to _exec fails */
  printf("Exec failed. retval = %i, errno = %i\n", retval, errno);
  perror("execl");

  return 0;
}
If we want to have the system find the command in the environment (the PATH variable), we can use this:
  /* Using a list and getting path from the environment */
retval = execlp("notepad", "notepad", "c:\\somefile.txt", NULL);
Passing a vector to exec:
#include <stdio.h>  /* printf, perror */
#include <unistd.h> /* exec functions */
#include <errno.h>  /* errno          */

int main(void)
{
  char *args[] = {"c:\\Windows\\system32\\notepad", "c:\\eula.1040.txt", NULL};
  int retval;

    /* Using a vector only (must specify path) */
  retval = execv("c:\\Windows\\system32\\notepad", args);

    /* We only reach this if the call to _exec fails */
  printf("Exec failed. retval = %i, errno = %i\n", retval, errno);
  perror("execl");

  return 0;
}
If we want to have the system find the command in the environment (the PATH variable), we can use this:
  /* Using a vector and getting path from the environment */
retval = execvp("notepad", args);
It may seem redundant to specify the name of the program twice. The first occurrence is the program that the OS is going to run. The second occurrence will be placed into argv[0] for the program to be run. Common sense might indicate that they should be the same, but it isn't enforced.
  char *args[] = {"notepad", "c:\\somefile.txt", NULL};
  execvp("notepad", args);
or
execlp("notepad", "notepad", "c:\\somefile.txt", NULL);
Launching Firefox and opening several tabs:
#include <stdio.h>  /* printf, perror */
#include <unistd.h> /* exec functions */
#include <errno.h>  /* errno          */

int main(void)
{
  const char *program = "c:\\Program Files\\Mozilla Firefox\\firefox.exe";
  char *args[] = {"http://www.digipen.edu/",
                  "http://www.gamedev.net/",
                  "http://www.ddj.com/",
                  "http://www.microsoft.com/",
                  "http://www.opera.com/", 
                  "http://www.acmqueue.com/",
                  "http://developers.slashdot.org/",
                  "http://www.artima.com/index.jsp",
                  "http://www.provantage.com/",
                  "http://www.tomshardware.com/",
                  "http://www.theinquirer.net/",
                  "http://arstechnica.com/",
                  NULL
                 };
  int retval;

    /* Using a vector only (must specify path) */
  retval = execv(program, args);

    /* We only reach this if the call to _exec fails */
  printf("Exec failed. retval = %i, errno = %i\n", retval, errno);
  perror("execl");

  return 0;
}
The spawn family of functions is similar to the exec family of functions, but has slightly different capabilities. These are available on Windows and may not be present with other systems. The behavior of these functions depends on the value of the mode parameter and can be one of these:
Notes