Skip to Content

Introduction to C Programming

This page presents the basics of C. Much of it applies to the non-OOP parts of C++

(for C++, use g++ as the compiler instead of gcc)

This is from old CS271 notes…


Another nice, extensive resource on C is from Prof. James Aspnes, and the whole page has lots of great stuff.


This course (CS 271) assumes that you have had a basic programming course, most likely in the Java programming languages.

Therefore, we do not need to spend time on basic datatypes, assignment statements, if-else constructs, and basic looping.

We also do not need to spend too much time on the basic idea of procedures and functions, call-by-value and call-by-reference parameters (also known as pass-by-value and pass-by-reference).

We will spend time on these in regards to their specific C/C++ features and nuances, but not on the general concepts.

Structure of a C Program

The structure is pretty simple:

  • include files
  • function prototypes
  • global declarations
  • function definitions (including main)

These parts don’t really have to be in any order (except as constrained by how they are used) and can span multiple files.

A simple program that demonstrates all of these:

/*-----------------------------------------------
// Sample C program
//
// Jonathan Cook
//---------------------------------------------*/
#include <stdio.h>
#include <math.h>

/*-----------------------------------------------
// Function prototypes
//---------------------------------------------*/
int SumPlusTwo(int v1, int v2);

/*-----------------------------------------------
// Global variables
//---------------------------------------------*/
int Total;

/*-----------------------------------------------
// Main: startup procedure for a C program
//   Input: command-line arguments, but this 
//          program does not process them
//   Output: returns 0 on success (always)
//---------------------------------------------*/
int main(int argc, char* argv[])
{
   int m;
   m = 5;
   Total = SumPlusTwo(22, m);
   printf("the total is: %d\n", Total);
   return 0;
}

/*-----------------------------------------------
// SumPlusTwo: add arguments, plus 2 more
//   Input: two integer variables
//   Output: returns sum of inputs plus 2
//---------------------------------------------*/
int SumPlusTwo(int v1, int v2)
{
   return v1 + v2 + 2;
}

Compiling and linking

gcc myprog.c (produces executable a.out)

gcc -o myprog myprog.c (produces executable myprog)

gcc -c myprog.c (produces object file myprog.o)

gcc -o myprog myprog.o otherfuncs.o (links object files together into executable file myprog)

Integral C/C++ Datatypes

C has several integer-type datatypes. These are:

C datatype Meaning
char signed 8-bit integer
short small signed integer, at least 16 bits
int “regular” integer variable, today usually 32 bits
long “big” integer variable, today often 32 or 64 bits
long long Extra-big integer variable, at least 64 bits

Each of the integer datatypes also have an unsigned version. The regular version holds both negative and positive numbers. The unsigned version holds only positive numbers and 0.

Because computers are finite, and the number of bits represents the amount of information a variable can hold, each datatype has it’s own limited range of values that it can hold. Unfortunately, the C programming language never defined exactly how big each integral datatype should be.

Number of bits Signed range Unsigned range
8 -128 to +127 0 to 255
16 -32,768 to +32,767 0 to 65,535
32 -2,147,483,648 to 2,147,483,647 0 to 4,294,967,295
64 about +/- 9,223,372,036,854,775,807 (9.2e18) 0 to 18,446,744,073,709,551,615 (1.8e19)

The way computers operate, these ranges are cyclical. If you have a variable with its maximum value and add one to it, you’ll get the minimum value (no matter whether signed or unsigned). That means you as the programmer needs to be responsible and make sure that this type of overflow does not happen.

For a discussion on Java datatypes, go to https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html .

Real Valued Datatypes

Like Java, in C the real value datatypes are named float and double. Unlike the integer datatypes, they do have a well-defined size. This is because they follow an IEEE standard for the bit representation format. Thus we have:

Datatype Bits Range Decimal Precision
float 32 (+-)10E-38 to (+-)10E+38 7
double 64 (+-)10E-308 to (+-)10E+308 15
long double 128 (+-)10E-4931 to (+-)10E+4931 33

What do you think this program will print out?

#include <stdio.h>

int main(int argc, char* argv[])
{
   float rval;
   int i;
   rval = 1000;
   printf("initial rval = %f\n", rval);
   for (i=0; i<10000; i++)
      rval += 0.000001;
   printf("new     rval = %f\n", rval);
   rval += 0.01;
   printf("final   rval = %f\n", rval);
   return 0;
}

Try it and find out!

Arrays

Arrays are pretty simple in C/C++.

  • Any datatype can be declared as an array.
  • All arrays begin at index 0.
  • All arrays are single-dimensioned. If you need a 2D array, you must declare an array of arrays (e.g., int TwoD[5][10] creates a 5-row by 10-column array).

Of course, programming with arrays can be tricky, but arrays themselves are simple!

Strings (plain C strings)

NOTE: The C++ standard library has a std::string class that is a full-featured object-oriented representation of strings. But plain C strings still occur in many places in C++ programs, and so understanding them is still crucial.

Computers are essentially number processors. Anything non-numeric, such as colors on your screen, or textual strings, must be represented numerically. This is why the char datatype is treated as an integer – it really is an integer! For example, the character ‘A’ to a computer is really the number 65. We use a standard called ASCII (American Standard Code for Information Interchange), and it assigns a unique value for each character, digit, and punctuation symbol that we typically use, along with some other stuff.

You might think that a string can just be an array of char’s. You’re mostly right, except that in C, and for a sequence of char values to be an official string, the last value must be a null character, which is a char value of 0, and is represented as ‘\0’.

So, a C string is a sequence of zero or more non-null character values ending with a single null character.

Why zero or more? Well, an empty string is a valid C string, and it is represented by one character, whose value is ‘\0’!

The important thing to remember is that C strings always take up one extra character location. Thus it is absolutely wrong to write:

   char mystr[5];
   strcpy(mystr, "hello");

Note that strcpy() is a C library function that copies strings, from the second parameter to the first parameter. But the string “hello” needs an array of at least 6 characters to be correctly stored!

Implicit and Explicit Type Conversion

C is known as a loosely typed language. If it was strict, every assignment and every operation would have to produce exactly the correct datatype that is needed. For example, if we were assigning the result of the expression “2+2” to a double variable, strict typing would give us an error, since the expression produces an integer! With strict typing, we would have to write something like “2.0+2.0”.

However, super-strict typing generally annoys programmers. We would rather just say “You know what I mean, so do it!” to our programming language. And C does try to do this. C will, in many cases, automatically convert, or promote your types to match what is needed.

You can generally think of these relations like:

char < 
  short < 
    int < 
      unsigned int < 
        long int < 
          unsigned long <
            long long int < 
              float < 
                double < 
                  long double

In general, C will implicitly move down this chain. If you want to move up, you have to explicitly tell it to. The exception is that C will move up the chain in the float/double/long-double datatypes, losing precision and possibly losing values. Notice that it is unsafe to move from int to unsigned int, but C will do it anyways!

Explicit Conversions

Any time you need to convert a value in a direction that C will not do implicitly, you can always do an explicit type conversion. You do that by writing (newtype) in front of the value/variable/expression whose value you want converted to some newtype.

For example, if I write “(int) 3.75”, C will convert the real value 3.75 into an integer value. To do this it must somehow lose the fractional part. C never rounds, it truncates! The value resulting from the above conversion is 3, not 4. It would be 3 even if we wrote “(int) 3.99999”.

If you want rounded conversions, you have to use the math library’s round() function.

More on Conversions

Integer conversions are described (almost unreadably so!) in the ANSI C spec as:

If both operands have the same type, then no further conversion is needed.

Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.