CS140 Lecture notes -- Migration from C++ to C


We're going with the assumption that you learned C++ in your introductory course (ECE 206 or CS302). In this course, we will use the language C, which is a predecessor to C++.

There are several important differences between C and C++, and we will spend the first part of the class learning how to do things in C, rather than in C++.


G++ vs. GCC

To compile a C++ program, you use the g++ compiler. For C, you use the gcc compiler. You will find that you can compile C programs generally with g++. However in this class, you will use gcc. You will not get credit for programs that compile and run correctly with g++ but not with gcc. Sorry.

Comments

Comments in C are not line based like in C++. Instead, you start a comment with "/*" and end it with "*/". It would be really nice if you could nest comments, but you can't.

So, simple examples:

// Here is a typical C++ comment.
int main()
{
  return(0);
}

/* Here is a typical C++ comment. */
int main()
{
  return(0);
}

Some C compilers will accept C++ comments, but some won't. Do not get into the habit of using C++ comments in C programs.


Header Files

In C++, all of our programs started with some header stuff, usually:

#include <iostream>
using namespace std;

In C, our programs start with different headers:

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


putchar() and printf()

One of the reasons that C++ is an attractive beginning language is that it makes the task of input and output relatively easy. With C, it's a little harder. In particular, you have none of the following to use:

I'm going to walk you through two procedures in C: putchar() and printf().

First, take a look at putchar(). This is a procedure which takes an integer (or char) and prints out its ASCII value on standard output. So, take a look at the putchar() version of "Hello World:" phw.c.

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

int main()
{
  putchar('H');
  putchar('e');
  putchar('l');
  putchar('l');
  putchar('o');
  putchar(' ');
  putchar('W');
  putchar('o');
  putchar('r');
  putchar('l');
  putchar('d');
  putchar('!');
  putchar('\n');

  return 0;
}

It works, but it's yucky:

UNIX> gcc -o phw phw.c
UNIX> ./phw
Hello World!
UNIX> 
In phw2.c, I have a better version, which puts "Hello World" into a character array (which we called a "C-Style string" in CS102), and then we traverse it with a for loop and use putchar() to print out each element:

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

int main()
{
  char *hw = "Hello World!\n";
  int i;

  for (i = 0; hw[i] != '\0'; i++) putchar(hw[i]);
  return 0;
}

You'll find putchar() to be useful in a variety of situations; however, 99% of the time, when you want to print output, you will use printf(). Printf() is a bizarre procedure in that it can take a variety of parameters. The first parameter is called a format string, which is a C-style (char *). In its simplest form, printf() takes use that one parameter and prints it out.

So, for example, the printf() version of "Hello World" (in pfhw.c) is even simpler than our previous putchar() example:

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

int main()
{
  printf("Hello World!\n");
}

UNIX> gcc -o pfhw pfhw.c
UNIX> pfhw
Hello World!
UNIX> 

The power of printf() arises when you put what are called conversion specifications into the format string. These always start with a percent sign. The common ones are:

When you put a conversion specification into your format string, it says to print out the next parameter in the specified format. So, for example, the following program (p10.c) prints the integers from 1 to 10, then the capital letters, then the capital and lowercase letters:

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

int main()
{
  int i;

  /* Print out numbers from 1 to 10 */

  for (i = 1; i <= 10; i++) printf("%d\n", i);

  /* Print out all the capital letters */

  for (i = 0; i < 26; i++) printf("%c", 'A'+i);
  printf("\n");

  /* Print out all the capital & lowercase letters: */

  for (i = 0; i < 26; i++) printf("%c%c", 'A'+i, 'a'+i);
  printf("\n");

  return 0;
}

Note, in that last printf() statement, there are two conversion specifications, and therefore there need to be two parameters following the format string.

UNIX> gcc -o p10 p10.c
UNIX> ./p10
1
2
3
4
5
6
7
8
9
10
ABCDEFGHIJKLMNOPQRSTUVWXYZ
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz
UNIX> 
If you want to print a percent sign, simply use two percent signs in the string. Similarly, if you want a double-quote, prepend it with a backslash.

Some Common Printf Mistakes

Take a look at: pfmistake.c. It has examples of five common printf() mistakes:

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

int main()
{
  int i;
  double d;

  /* Forgetting a parameter */

  printf("1. %d\n");

  /* Trying to print an integer as a double without casting it */

  i = 5;
  printf("2. %lf\n", i);

  /* Trying to print a double as integer without casting it */

  d = 5;
  printf("3. %d\n", d);

  /* Trying to print a string as an integer, even though it's a string of an integer */

  printf("4. %d\n", "5");

  /* Trying to print an integer as a string */

  printf("5. %s\n", i);

  return 0;
}

Here's the program running on my MacBook:

UNIX> gcc -o pfmistake pfmistake.c
UNIX> ./pfmistake
1. -1881117246
2. 0.000000
3. 0
4. 8152
Bus error
UNIX> 
And here's it running on hydra3:
UNIX> gcc -o pfmistake pfmistake.c
UNIX> ./pfmistake
1. 1075338880
2. 0.000000
3. 0
4. 134513994
Segmentation fault
UNIX> 
On the first four mistakes, you're likely to get a random answer, depending on the kind of machine on which you are running. On the last mistake, you are going to get a bus error or segmenetation violation in almost all cases, because you are treating an integer as a pointer.

Simple reading of standard input: scanf()

. Scanf() allows you to read formatted input from standard input. It is like printf() in that it takes a format string as its first argument. However, that string is usually composed simply of conversion specifications. In fact, I will recommend that you always use only one conversion specification in each of your scanf() statements. You will never see me do otherwise.

The big difference between printf() and scanf() is that you have to provide a pointer to the argument that you want to be read from standard input. So, for example, the following program reads in an integer. Study it carefully (scex):

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

int main()
{
  int i;

  printf("Enter an integer: ");
  scanf("%d", &i);
  
  printf("You entered: %d\n", i);
  return 0;
}

Note, we passed &i rather than i. This is because scanf() needs the pointer to "fill in" the value of i. If you put i instead of &i, you'll probably get a segmentation violation, which, believe it or not is a good thing since it will alert you to your problem.

Here is a quick example of the program running:

UNIX> gcc -o scex scex.c
UNIX> ./scex
Enter an integer: 5
You entered: 5
UNIX> 
That's nice. Here are some weirder examples:

UNIX> ./scex
Enter an integer: 55.99
You entered: 55
UNIX> ./scex
Enter an integer: 45Fred
You entered: 45
UNIX> ./scex
Enter an integer: 0000000000000005
You entered: 5
UNIX> ./scex
Enter an integer: Fred
You entered: -1073746852
UNIX> ./scex
Enter an integer: <CNTL-D>
You entered: -1073746852
UNIX> 
If you specify for scanf() to read an integer, it will read the next word and try to convert it to an integer as long as it starts like one. When it hits some characters that don't make sense, it ignores them and returns what it had read so far. This is why it returns 55 and 45 in the first two examples. Obviously, the next example shows that it doesn't care about leading zeroes. The last two exampmles show what happens when you don't give it an integer -- it returns and does not modify i. Since you never set i it has a random value.

So how do you deal with getting bad input or EOF? You use scanf()'s return value. Scanf() returns how many matches it made. If you only ever call it with one conversion specification (as I do), then if it returns 1, you had a match. If it doesn't, you don't. The following program (scex2.c) calls scanf() repeatedly to read integers until it fails:

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

int main()
{
  int i;
  int n;

  n = 1;
  while (1) {
    if (scanf("%d", &i) != 1) return 0;
    printf("Integer %d. %d\n", n, i);
    n++;
  }
}

When we run it, we see a few things. First, scanf() does not care about line breaks -- it simply keeps reading integers until it gets another, whether there are multiple integers on one line or not. It also does not care about blank lines.

UNIX> ./scex2
44 33 22
Integer 1. 44
Integer 2. 33
Integer 3. 22
-5 
Integer 4. -5



Fred
UNIX> 

Reading Strings with scanf()

When you read a string with scanf, you need to pass an array of characters. Why not a pointer? Because an array is a pointer to the first element of the array, and that's good enough for C. Scanf() will read the next word of standard input into that array, and turn it into a C-style string. For that reason, the array must be big enough to accomodate the inputted string, including the null character.. If not, you're in big trouble, which is why you should only read strings with scanf() if you know that your input is going to be small enough to fit into your array.

The following program (scex3.c) gives an example:

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

int main()
{
  char s[16];
  int i;

  i = 261303714;

  while (1) {
    if (scanf("%s", s) != 1) return 0;
    printf("s = %s.  i = %d\n", s, i);
  }
}

This program works fine as long as we enter words with 15 characters or fewer. That happens in the first few examples. However, when we give it 1234567890123456, which has 16 characters plus the null character, scanf() writes past the end of the array, and starts overwriting i. That happens in the last example too.

UNIX> gcc -o scex3 scex3.c
UNIX> ./scex3
Jim
s = Jim.  i = 261303714
Jim Plank
s = Jim.  i = 261303714
s = Plank.  i = 261303714
123456789012345
s = 123456789012345.  i = 261303714
1234567890123456
s = 1234567890123456.  i = 261303552
12345678901234578
s = 12345678901234578.  i = 261292088
<CNTL-D>
UNIX> 
Bugs of this sort are disastrous, and if you have ever heard of a buffer overflow attack, this is one of the sources. It is why you should be quite circumspect about using scanf() with strings.

No classes, no new, no delete

Finally, in C, we don't have class's, and we don't have new or delete. There's also no more private/public/protected, string class, constructors or destructors. We'll get to the replacements of each of these in subsequent lectures.