C programming provides greater flexibility than many other languages, with better control over your computer's memory.
iStockphoto/ThinkstockThe C programming language has gained widespread popularity, and it's easy to see why. C programming is efficient and offers the programmer significant control. Several other programming languages, such as C++, Java, and Python, were built upon C's foundation.
Although you may not use C exclusively in your programming career, the chances are that you'll benefit from learning it. Even if you don't use C frequently, understanding it is highly advantageous. Here's why:
You'll gain the ability to write and read code for software that can run on various computer platforms, ranging from small microcontrollers to desktop, laptop, and mobile operating systems.
You'll develop a deeper understanding of what high-level languages are doing behind the scenes, such as handling memory management and garbage collection. This knowledge will enable you to write more efficient programs.
If you're an IT professional, learning C can also be valuable. IT experts often write, maintain, and execute scripts as part of their work. A script is a sequence of commands for a computer's operating system to follow. To run specific scripts, the computer creates a controlled execution environment known as a shell. Since many operating systems use shells based on C, the C shell is a widely used scripting adaptation favored by IT professionals.
This article dives into the history of C, explains its significance, demonstrates basic C code examples, and examines key C features, such as data types, operations, functions, pointers, and memory management. While this isn't a step-by-step guide to C programming, it offers insights into what makes C programming stand out, beyond the first few chapters of a typical C programming manual.
Let's begin by exploring the origins of the C programming language, its evolution, and its role in modern software development.
What is C?
The simplest way to describe C is as a computer programming language, which allows you to write software that a computer can execute. This software could range from large applications, like a web browser, to small sets of instructions embedded in a microprocessor or other hardware.
C programming language was created in the early 1970s at Bell Laboratories, primarily through the work of Ken Thompson and Dennis Ritchie. It was developed because programmers required a more user-friendly way to write software for the UNIX operating system, which at the time required writing programs in assembly language. Assembly language programs, which interact directly with hardware, were long, complex to debug, and involved a lot of effort to add new features [source: King].
Ken Thompson’s initial attempt at creating a high-level language was called B, inspired by the BCPL system programming language. When Bell Labs obtained a DEC UNIX system (PDP-11), Thompson improved B to suit the capabilities of the new hardware. This led to the creation of C, which by 1973 was stable enough to rewrite UNIX using the new language [source: King].
Before C could be used widely beyond Bell Labs, programmers needed clear documentation on how to use it. In 1978, Brian Kernighan and Dennis Ritchie's book "The C Programming Language" was published, which became the authoritative guide for C programming. Known as K&R or the "White Book," it became the definitive resource, and its second edition, released in 1988, remains widely available today. The pre-standard version of C is referred to as K&R C, named after the book.
To prevent the creation of various dialects over time, C developers dedicated the 1980s to establishing standards for the language. The U.S. standard for C, American National Standards Institute (ANSI) standard X3.159-1989, was officially recognized in 1989. Following that, the International Organization for Standardization (ISO) introduced the ISO/IEC 9899:1990 standard in 1990. Subsequent versions of C, such as C89, C90, and C99, are based on these standards and their updates. You may also come across C89 being called "ANSI C," "ANSI/ISO C," or "ISO C."
C’s role in UNIX was just one aspect of the operating system explosion of the 1980s. Despite its enhancements over older systems, C was still not the most user-friendly language for developing larger software applications. As computers became more powerful, the need for an easier programming experience grew. This led programmers to create their own compilers and, in turn, new programming languages based on C. These new languages aimed to simplify coding for complex tasks with many components. For example, C++ and Java, both derived from C, made object-oriented programming easier, allowing programmers to reuse code more efficiently.
Now that you have some background knowledge, let's dive into the inner workings of C itself.
Editing and Compiling C Code
C is known as a compiled language, meaning you must use a compiler to transform the code into an executable file before running it. The code is written in one or more text files, which you can open, view, and modify in any text editor, such as Notepad on Windows, TextEdit on a Mac, or gedit on Linux. An executable file is one that the computer can execute. The compiler checks the code for errors and, if everything is correct, generates the executable file.
Before diving into the details of C code, let's make sure we can locate and use a C compiler. If you're on Mac OS X or most Linux distributions like Ubuntu, you can install a C compiler by adding the development tools software for your specific operating system. These C compilers are typically command-line tools, meaning you'll run them through a terminal window. To execute one, simply type "cc" or "gcc" followed by additional command line options and arguments, which are words you enter after the command before pressing Enter.
If you're using Microsoft Windows, or prefer a graphical interface over the command line, you can install an Integrated Development Environment (IDE) for C programming. An IDE consolidates your coding, compiling, testing, and error correction into one interface. For Windows users, Microsoft Visual C++ is an option, which supports both C and C++ programming. Another popular, free IDE is Eclipse, a Java-based platform that works on Windows, Mac, and Linux, and includes extensions for compiling C and other programming languages.
The version of the compiler you use is crucial for C programming, just like with any other programming language. It's important to use a C compiler version that matches or exceeds the version of the C language you're programming with. If you're working in an IDE, make sure to adjust the settings to match the C version you are using. In the case of a command line, you can add an argument to specify the compiler version, like this example command:
gcc –std c99 –o myprogram.exe myprogram.c
In the above command, "gcc" calls the compiler, and everything that follows are command line options or arguments. The "-std" flag is used with "c99" to instruct the compiler to use the C99 version of the C language standard. The "-o" flag specifies the name of the output executable file, in this case, "myprogram.exe"; without it, the file would default to "a.out". The final argument, "myprogram.c", is the C code file to compile. Essentially, the command tells gcc, "Compile myprogram.c using the C99 standard and save the output as myprogram.exe." You can always find a full list of options specific to your compiler online, whether you're using gcc or another tool.
Now that you have your compiler set up, you're all set to begin programming in C. Let's kick things off by examining the fundamental structure of one of the simplest C programs you could possibly write.
The Simplest C Program
Let's explore a simple C program that will help us understand the basic principles of the language and the C compilation process. If you have a C compiler installed on your machine as described earlier, you can create a text file called sample.c and follow along with us as we break down this example. Just remember, if you forget to include the .c extension or if your text editor automatically adds a .txt extension, you may encounter an error when you attempt to compile it.
Here's the program we're going to use for this example:
/* Sample program */
#include
int main()
{
printf("This is output from my first program!\n");
return 0;
}
Once compiled and run, this program directs the computer to display the message "This is output from my first program!" and then cease. It doesn't get simpler than that! Now, let's break down each line's function:
Line 1 -- This is a method for adding comments in C, enclosed between /* and */ on one or more lines.
Line 2 -- The #include directive instructs the compiler to search for additional C code, primarily in libraries, which are files containing common reusable functions. This one refers to a standard C library that provides functions for taking input from the user and displaying output on the screen. We will dive deeper into libraries later.
Line 3 -- This marks the first line of a function definition. Every C program includes at least one function, a block of code that performs an action when the program runs. The function completes its task and generates a return value, which can be used by other functions. The program, at the very least, contains a function named 'main', like the one here, with an 'int' return type, meaning an integer. Later, we will explore the meaning of the empty parentheses.
Lines 4 and 7 -- Function instructions are enclosed in braces. Some developers prefer to place the opening and closing braces on separate lines, as shown here. Others prefer to place the opening brace ({) at the end of the function definition's first line. While it's not necessary for each line of code to be on its own line, most programmers choose to put each instruction on a new line, indented for readability and easier future editing.
Line 5 -- This is a call to a function named printf. The function is defined in the stdio.h library that we included in Line 1, so you don't have to write it yourself. This call tells printf what to display on the screen. The \n inside the quotes is not printed but is an escape sequence that tells printf to move the cursor to the next line. Notice that every statement inside the function ends with a semicolon.
Line 6 -- Any function that returns a value must include a return statement like the one here. In C, the main function must always have an integer return type, even though it's not actually used in this program. It's important to note that when you run a C program, you're running its main function. Therefore, during testing, the return value can be displayed to indicate the program's execution outcome. A return value of 0 is preferred, as programmers often look for this value to confirm that the program ran successfully.
Once you're ready to test the program, save the file, then compile and run it. If you're using the gcc compiler from a command line, and the program is in a file named sample.c, you can compile it with the following command:
gcc -o sample.exe sample.c
If your code is error-free, you should see a file called sample.exe in the same folder as sample.c after executing the command. A common mistake is a syntax error, such as a missing semicolon at the end of a line, or unclosed parentheses or quotes. To fix this, open the file in your text editor, make the necessary corrections, save it, and run the compile command again.
To execute the sample.exe program, use the following command. Pay attention to the ./ prefix, which instructs the computer to search in the current directory for the executable file:
./sample.exe
These steps cover the fundamentals of coding and compiling in C, but there's much more to learn about compiling. If you're interested in a deeper understanding, explore additional C programming resources. Now, let’s dig deeper into the building blocks C provides for creating programs.
Common Programming Concepts in C
Now, let’s dive into how to apply some of the common programming concepts in your C code. Here’s a brief overview of these essential concepts:
Functions -- As mentioned earlier, a function is a block of code that tells the computer what to do when the program executes. Some languages call them methods, but in C, we typically don’t use that term. Your program may define multiple functions and call them from other functions. We’ll explore the structure of C functions in greater detail later.
Variables -- When you run a program, there are times when you want the flexibility to execute it without having all the values predetermined. Just like in algebra, C allows you to use variables as placeholders for values that are unknown or yet to be determined.
Data types -- To store data in memory during program execution and to understand which operations can be performed on that data, a programming language like C defines specific data types. Each type in C has a distinct size, measured in binary bits or bytes, and a set of rules for how its bits are interpreted. We’ll soon see how selecting the right data type is crucial when working in C.
Operations -- In C, you can perform arithmetic operations (like addition) on numbers and string operations (such as concatenating strings of characters). C also has built-in operations tailored for tasks involving your data. We’ll briefly review these operations when we look at C’s data types.
Loops -- One of the most fundamental tasks for a programmer is to repeat an action multiple times depending on conditions that arise as the program runs. A loop is a block of code designed to run again and again based on specified criteria. C provides several types of loops for this purpose: while, do/while, for, as well as control statements like continue, break, and goto. Additionally, C supports standard conditionals such as if/then/else and switch/case statements.
Data structures -- When working with large sets of data, and needing to sort or search through it, you'll often use data structures. A data structure is a way to organize and store multiple pieces of data of the same type. The most common data structure in C is an array, which is essentially an indexed list with a set size. While C provides libraries to work with common data structures, you can also create custom structures and functions as needed.
Preprocessor operations -- Sometimes, before compiling your code into an executable, you need to instruct the compiler on how to modify the code. These preprocessor operations can include things like replacing constants or pulling in code from external C libraries, as demonstrated earlier in the sample code.
C also requires you to manually handle certain concepts that many modern programming languages handle automatically. These include pointers, memory management, and garbage collection. We'll cover these important concepts later in this guide to ensure you're comfortable with them when writing C programs.
This quick summary of programming concepts may feel overwhelming if you're new to coding. Before diving into a more technical C programming guide, let's take a simpler, more approachable look at some of the core concepts listed here, starting with functions.
Functions in C
In most programming languages, you're able to create functions, which allow you to break up long programs into smaller, manageable chunks. These functions can then be reused multiple times within the program. While some languages, particularly object-oriented ones, might refer to these as methods, C refers to them as functions.
Functions take inputs known as parameters, and they produce an output as a result. The collection of code that makes up a function is called the function definition. Here's the basic syntax for defining a function:
{
return
}
Every C program must include a function called main. This function is the entry point of the program, and the compiler always looks for it to begin execution, even if main calls other functions. Here’s the main function we encountered in the simple C program earlier. It returns an integer, doesn’t take any parameters, and contains two statements, one of which is the return statement:
int main()
{
printf("This is output from my first program!\n");
return 0;
}
Functions, apart from main, consist of a definition and one or more calls to other functions. A function call is simply a statement or a portion of a statement inside another function. To invoke a function, its name is followed by parentheses. If the function requires parameters, the call must pass corresponding values to match these parameters. This act of supplying values is called passing parameters to the function.
What exactly are parameters? In the context of a function, a parameter refers to a piece of data that the function needs to perform its task. In C, functions can accept any number of parameters, also known as arguments. Each parameter in a function definition must define two things: its data type and the name it will use within the function body. If a function has multiple parameters, they are separated by commas. Consider the following function, which uses two integer parameters:
int doubleAndAdd(int a, int b)
{
return ((2*a)+(2*b));
}
Next, let's broaden our view of functions by exploring how they integrate within a larger C program.
Function Prototypes
In C, you can place a function definition anywhere in the program, as long as it’s not inside another function. However, the compiler must know about the function’s existence beforehand. This is achieved by including a function prototype at the start of your code. The prototype looks like the first line of a function definition, but in C, you don’t need to specify parameter names—just their data types. For example, the function prototype for doubleAndAdd would look like this:
int doubleAndAdd(int, int);
Think of function prototypes as a packing list for your program. Before the compiler assembles your program, it checks this list to ensure all necessary components are in place, much like you would check a list before assembling a new bookshelf.
If you're working through the sample.c program we reviewed earlier, open the file and modify it by adding the function prototype, definition, and function call for the doubleAndAdd function as demonstrated here. Then, compile and execute the program just like before to observe how the updated code behaves. You can use the following example to guide your experiment:
#include
int doubleAndAdd(int, int);
int main()
{
printf("This is the output from my very first program!\n");
printf("If you double 2 and add 3, the result is: %d \n", doubleAndAdd(2,3));
return 0;
}
int doubleAndAdd(int a, int b)
{
return ((2*a) + (2*b));
}
We've explored some basic structural components of a C program so far. Now, let's dive into the types of data that can be manipulated in C and the operations available for those data types.
In C, you might often come across the term 'function declaration', particularly from older C programmers. However, for clarity, this article uses the term 'function prototype' as it conveys a crucial distinction. Originally, a function declaration only required the return type, function name, and an empty pair of parentheses. A function prototype, on the other hand, provides the compiler with essential information by detailing the number and data types of the parameters the function will use. Today, using function prototypes is considered best practice in C and many other programming languages.
Data Types and Operations in C
From the perspective of your computer, everything is represented as a binary sequence. Data types in C specify how these bits are interpreted.
Hemera/ThinkstockTo your computer, data is simply a collection of ones and zeros that signify the on/off states of the electronic bits in your system's memory or processor. It's the software that interprets this binary data. C stands out among high-level languages by offering direct manipulation of bits as well as abstraction through data types.
A data type is a set of rules that defines how a sequence of bits should be understood. It comes with a defined size and specific operations (like addition and multiplication) that can be performed on that data. In C, the size of a data type depends on the processor. For instance, on a 16-bit processor, an integer (int) is 16 bits long, but on 32-bit and 64-bit processors, it is 32 bits long.
One key concept C programmers should understand is the difference between signed and unsigned data types. A signed data type reserves one bit to indicate whether the value is positive or negative. For example, on a 16-bit system, an unsigned int can store values from 0 to 65,535, whereas a signed int can store values from -32,768 to 32,767. If an operation results in a value that exceeds the range of the data type, the programmer must account for the overflow with additional code.
Considering the unique limitations and characteristics of C's data types and operations, C programmers must carefully choose the right data types for their programs. They can select from the primitive data types that are built into C. For a comprehensive list of C's data types and important information on how to convert between types, consult a reliable C programming guide.
C programmers also have the option to define their own data structures, which combine primitive data types and related functions to manage and manipulate the data. Although data structures are an advanced topic that goes beyond this article, we'll explore one of the most fundamental structures: arrays. An array is a collection of data elements, all of the same type. While the size of an array is fixed, its contents can be copied into arrays of different sizes.
While arrays of numbers are common, strings (arrays of characters) offer some of the most distinctive features. A string lets you store a sequence of characters, like 'hello,' which can be input by the user or displayed on the screen. String handling in C is so specialized that it has its own library, 'string.h,' providing various functions to manipulate strings.
The operations in C are quite typical of most programming languages. When combining multiple operations into a single statement, it's crucial to understand operator precedence, or the order in which the program will evaluate the operations in an expression. For instance, (2+5)*3 equals 21, but 2+5*3 equals 17, because C evaluates multiplication before addition, unless parentheses dictate otherwise.
If you're getting started with C, it's essential to become comfortable with its primitive data types and operations, as well as the precedence of operations when multiple operations are combined in a single expression. Experimenting with different types of variables and operations will help you gain hands-on experience and a deeper understanding of how C handles data.
By now, you've covered some fundamental C concepts. But next, we'll explore how C allows you to write programs efficiently without having to reinvent the wheel each time.
Don't Start from Scratch, Use Libraries
Libraries play a crucial role in C because the language itself only provides the most basic functionality. For instance, C doesn't have built-in input-output (I/O) functions for reading from the keyboard or printing to the screen. Any functionality beyond the core must be created by the programmer. When code is valuable across multiple programs, it's often bundled into a library for reuse, making development more efficient.
So far, we've already encountered one library in C: the standard I/O (stdio) library. The #include directive at the top of the program told the C compiler to load the library from its header file, stdio.h. C provides a variety of standard libraries, such as those for I/O, math functions, time manipulation, and common operations on data structures like strings. Check online or refer to your C programming guide for more information on the C89 standard library and updates introduced in C99.
You have the ability to create your own C libraries. By doing so, you can break down your program into reusable sections, making your code modular. This not only allows you to easily reuse the same code across different programs but also results in shorter, more readable files that are easier to test and debug.
To access the functions in a header file, simply add a #include directive at the start of your program. For standard libraries, use the header file's name within angle brackets. For libraries you've created, use double quotes around the file name. Unlike other parts of your C code, you don't need to add a semicolon at the end of the #include line. Here's an example of both types of libraries being included:
#include
#include "mylib.h"
A good C programming guide will show you how to write your own libraries. The function definitions you'll write are the same whether they're part of a library or within your main program. The key difference is that you'll compile the functions separately into an object file (with the .o extension) and create a header file (with a .h extension) containing the function prototypes for each library function. In your main program, you reference the header file with a #include directive, and you pass the object file as an argument to the compiler each time you compile the program.
So far, we've covered C concepts that are quite common in other programming languages. Now, let's shift gears and explore how C handles memory management on your computer.
Some Pointers about Pointers in C
When your C program is loaded into memory (typically in RAM), each part of the program is assigned a specific memory address. This includes the variables that hold data. Each time you call a function, the function and its related data are loaded into memory temporarily for execution. If the function takes parameters, C makes a copy of the value to use inside the function.
However, in certain situations, you may want to modify the original data directly in memory. If C creates a copy of the data for use in a function, the original remains unchanged. To alter the original data, you must pass a pointer to its memory address (passing by reference), rather than just its value (passing by value).
Pointers are an essential part of C programming. To fully grasp the language, understanding pointers is key. A pointer is a variable designed to store the memory address of another piece of data. It also has a data type, which allows it to correctly interpret the bits stored at that memory location.
At first, you might find it difficult to spot the pointer when looking at two variables in C code. Even experienced programmers can face this challenge. However, when you declare a pointer for the first time, it becomes more apparent since an asterisk is placed immediately before the variable name. This asterisk is called the indirection operator in C. The following example shows the creation of an integer 'i' and a pointer 'p' that points to an integer:
int i;
int *p;
At this point, neither 'i' nor 'p' holds any value. Next, we'll assign a value to 'i' and make 'p' point to the memory address of 'i'.
i = 3;
p = &i;
Here, the ampersand (&) serves as the address operator, placed right before 'i', indicating 'the address of i'. You don't need to know the specific address of 'i' to perform this assignment. In fact, the address will most likely change each time the program is run! The address operator ensures that the correct address is determined during program execution. Without the address operator, the assignment 'p = i' would assign the value of 3 as a memory address, which is incorrect, as it would be referring to the value of 'i' rather than its address.
Now, let's dive into how pointers are used within C code and explore the potential challenges you should be prepared for.
Using Pointers Correctly in C
To excel in C programming, it's essential to have a strong understanding of how to properly use pointers within your code.
©iStockphoto.com/DSGproOnce you've got a pointer, you can substitute it for a variable of the same data type in both operations and function calls. In the example below, the pointer to 'i' is used in place of 'i' in a larger expression. The asterisk (*) in front of 'p' tells the program to use the value stored at the memory address 'p' points to, rather than the address itself:
int b;
b = *p + 2;
Without pointers, it's almost impossible to break tasks down into functions outside of the main function in a C program. For example, if you've created a variable in 'main' called 'h' to store the user's height in centimeters, and you have a function 'setHeight' to prompt the user to set that value, the main function could look like this:
int h;
setHeight(h); /* This may cause an issue. */
The function call attempts to pass the value of 'h' to 'setHeight'. However, after the function completes, 'h' remains unchanged because only a copy of 'h' was used, which is discarded once the function execution finishes.
To modify 'h' itself, ensure that the function can accept a pointer to the existing value rather than working with a new copy. Therefore, the first line of 'setHeight' should accept a pointer instead of a value, indicated by the indirection operator:
setHeight(int *height) { /* Implement the function here */ }
You have two options for calling the setHeight function. One option is to directly pass the address of h using the address operator (&h). The other option is to first create a pointer to h and pass the pointer instead. Both methods are demonstrated below:
setHeight(&h); /* Passing the address of h to the function */
int *p;
p = &h;
setHeight(p); /* Passing a pointer to the address of h to the function */
One common issue with pointers in programming is when multiple pointers refer to the same value. Any changes made to that value will simultaneously affect all its pointers. Whether this is beneficial or problematic depends on the situation and goals within your program. Mastering pointers is essential to mastering C programming, so it's crucial to practice and get comfortable with them to handle these challenges effectively.
The features we've discussed in C up to this point aren't unique to it alone; many other programming languages share similar characteristics. However, in the next section, we'll dive into C's specific approach to managing memory carefully and effectively.
The Importance of Memory Management in C
C's flexibility is largely due to its ability to scale programs down to work with very limited memory. This was particularly important when C was first created, given the relatively low processing power of early computers. Today, with the rise of smaller electronics, such as mobile phones and compact medical devices, there's renewed focus on maintaining minimal memory usage in software. As a result, C remains the language of choice for developers who require precise control over memory management.
To grasp why memory management is crucial, consider how a program utilizes memory. When you launch a program, it occupies your computer's memory and starts running, sending instructions to and receiving data from the processor. For specific functions to run, the program temporarily loads them into separate memory regions, discarding the memory once the function completes. Furthermore, any data the program processes will also take up memory space for as long as it's in use.
If you want to take greater control over memory, dynamic storage allocation is key. C offers dynamic storage allocation, which lets you reserve memory when it's needed and release it once you're done. While many languages automatically manage memory and perform garbage collection, C gives you the freedom—and sometimes the necessity—to manually manage memory allocation with specific functions from the standard C library.
- malloc -- Short for memory allocation, malloc is used to reserve a block of memory of a specified size for storing data that your program needs. When you use malloc, you essentially create a pointer to that allocated memory. This step isn’t necessary for single data elements, like an integer, that are allocated upon their declaration (e.g., int i). However, it becomes crucial for managing complex data structures like arrays. Alternatives in C for memory allocation include calloc, which clears the memory during allocation, and realloc, which allows you to resize previously allocated memory.
- free -- The free function is used to release memory that was previously assigned to a pointer, allowing it to be reused.
The best practice when working with malloc and free is that any memory you allocate should be freed. Even when you allocate memory in temporary functions, it stays in memory until the system cleans it up. To keep memory available for future use, always free the memory before the function completes. This practice helps keep your program's memory usage efficient and prevents memory leaks, which occur when a program continues to use more memory than it releases, leading to crashes or freezes. However, be careful not to free memory that's still needed later in the same function.
In this article, you've explored the essential structure and core concepts of the C programming language. We've discussed its history, similarities with other languages, and the key features that make it a flexible and powerful tool for software development. Turn the page for more in-depth information and programming guides that will help you continue your C programming journey.
