C development on Linux – Types, variables, operators – III.

Introduction

As promised, starting with this part of our C development article, we will get started with learning, without further introduction. I couldn’t find no better way to start other than this, because types, operators and variables are an essential part of C and you will use them all the time when writing your own programs. For example, you can write a simple C program without defining your own functions, but it’s harder to do that without some variables, unless you wanna stick to “Hello, world!”. A variable is nothing more than a location in memory holding a value that can be altered (hence the name). But before you declare a variable you must know what kind of value you want it to hold, and here you will use types. And in order to operate on those variables, you’ll need…operators, of course. I intend to make this course as concise as possible, so I recommend attention and as usual, practice.

Types

As said, before you go and declare a variable, you must know what kind of value will it hold. Will it be a number? If so, how large could it possibly get? Is it an integer? Or maybe you want to declare a string? These are things you must know for certain before choosing the type, and we recommend extra care when it comes to possible buffer overflows. C is the kind of language that gives you enough rope to hang yourself and doesn’t do much hand-holding, and these errors are very hard to spot in a large program.

Before we start, you must be aware of relationships between hardware and types. This is where we expect you to do some reading for yourself especially if you’re using hardware other than x86, be it 32 or 64-bit, compilers other than gcc or operating systems other than Linux. Usually, these differences appear when dealing with floating point values. We won’t get deeper into this, as it’s not the time nor the place, but you are expected to read some documentation on your compiler, especially hardware-dependent parts. Now let’s start.

char c;
unsigned char uc;
short s;
unsigned short us;
int i;
unsigned u;
long l;
unsigned long ul;
float f;
double d;
long double ld;
const int ci;

We decided to take the path of “example first, explanations later” here, because we felt that some of you will find the above example familiar. There are other related languages that declare their variables in almost the same way, and after all, the keywords are intuitive. Before we go on, it must be said that char, int, float and double are the primary data types in C. Unsigned and signed are modifiers, meaning that if you need to work with values smaller than zero, you should tell the compiler that your variable is signed, as in it can be larger or smaller than zero. long and short (these are applicable to integers usually) allow you to store bigger values, or smaller, and the number of bytes is machine-dependent, but a short must be always smaller than an int, which in turn must be always smaller than a long. As you can see, in practice one does not use long int or short int, just long or short. The const keyword tells the compiler that once a variable has a value, it cannot be changed.

Let’s start with the smallest type, char. It is guaranteed to be large enough to hold one byte’s worth, and it’s always fixed size. If people will tell you that a byte is always eight bits, better think again. Every popular hardware architecture indeed uses eight-bit bytes, but there are exceptions, so don’t make assumptions if you want to write portable code. On x86, since a byte is eight bits, a char (unsigned) can hold values from 0 to 255, that is 28. If a char is signed, then it can hold values from -128 to 127. But the name may mislead you: a character can indeed be stored in a char, but if you’re using Unicode, we’re talking multibyte there and you’ll have to use wchar_t, but more on that later.

Now that you know what type modifiers are, we can get to integers. On integers, you can combine the sign and length modifiers, as seen in the example above, to fit your needs. Remember to have an editor handy and check with the limits.h header (on my system it’s to be found in /usr/include) to find out the actual limits on your system. As a short rule, an int will hold values from 0 to 65535 or, if signed, from -32768 to 32767. And a long modifier will double the number of storage bytes, so if an int requires 2 bytes, a long will require 4. We’ll leave it up to the user to figure out the rest of the integers and their minimal and maximal values. We will however, show you how to find out sizes and limits on your system.

floats are floating-point values, which implies that you must define a variable like this:

float value;
value = 234.00;

even if it doesn’t have nothing after the dot (the decimal part), so it’s an integer actually. There are actually situations where you must declare an integer value as a float, because the value might change and the declared type must be able to store floating point values. All the values on your machine can be found in float.h.

Variables

Now that you know what types you have available in C, let’s see how you can effectively use them. Some of might wonder “if we have long doubles that can store values so big, why not use them everywhere?” . Programming is about efficiency, and C programming especially so, and that’s why storing a value like 23 in a double will use 4 times the necessary memory, for nothing. When you declare a variable, a chunk of memory is reserved for it depending on the type. So why waste memory for no good reason? Create a habit of using the exact type that fits your (possible) values, not less, not more. You have seen above how to declare variables. Now let’s see how to define them, as in let’s give them a value.

c = 'a';
i = 234;
f = 12643.984;
ld = 16546581654161598309.87;

We took the names from the previous examples, which, as you may have noticed, are written to reflect the assigned type, so ‘ld’ is a long double and so on. In this example we took two steps: the first to declare the variable, the second to define it by assigning it a value. Some will say it’s good style to write code like that, but you can do both operations in one step and no one will hurt you:

char c = 'a';
int i = 234;
float f = 12643.984;
long double ld = 16546581654161598309.87;

We recommend and even urge you to use names with a meaning in your code, and comment it as much as possible: chances are there’ll be others reading what you wrote and their life will be so much easier if you do. Also, use caps only when necessary, especially since C uses all-caps in various preprocessor directives. Also the first character in the variable name must be a letter.

As promised, since all talk and no play ain’t good, we’ll show you a little program you can use to see the minimal and maximal values of various types, but we’ll just illustrate a few. The rest will be your job to do, following our example, with an editor having limits.h and float.h open. There will be some new elements here, but never worry, they will be explained.

#include <stdio.h>
#include <float.h>
#include <limits.h>

int main()
{
  unsigned long long ullmax = ULLONG_MAX;
  long lmax = LONG_MAX;
  long double ldmax = LDBL_MAX;
  
  printf("The max value of an unsigned long long is %Lu.\n", ullmax);
  printf("The max value of a long is %ld.\n", lmax);
  printf("The max value of a long double is %Lf.\n", ldmax);
  
  return 0;
}

So, we declare three variables with meaningful names and assign them the values of three macros defined in limits.h and float.h. Then of course, we’ll have to print them. We do that using printf(), and here we’ll stop for a little talk. We recommend ‘man 3 printf’ for further details on format strings, that is, the part inside printf’s double quotes that start with a ‘%’. They tell printf what kind of value it should expect, so it should behave differently with different types. In the first example ‘%Lu’ means long long (the L), which is unsigned (the ‘u’). For integers, the format string is ‘d’, for decimal, and because it’s a long integer, it will be’%ld’. In the third printf, f stands for float, a double is basically a long float, and a long double is a long long float, hence the format.

Now, save the above code, compile it and run it. This program, once you add more to it, will help you when you want to declare a variable, but you’re yet uncertain what type it should fit into.

Operators

Arithmetic operators

This subchapter, of course, deals with the usual basic operators you learned in primary school. But there’s a little more. Foe example,. the +, -, *, / and % operators are the binary operators. % is the modulo operator, meaning that if we have 50 % 2, the result will be 0 because the result of the division 50 / 2 has an integer as a result. You can use the first four operators with any numeric value, but modulo deals only with integers. Precedence is the same as in the arithmetics book.

Relational operators

These operators are >, >=, <=, <, and they all have the same precedence. For the next part we recommend extra care, because it’s cause for much confusion in the beginner’s league, and non-beginners alike. Like said above, one uses ‘=’ to give some value to a variable. But if you want to check if a variable has a certain value, you use ‘==’, and if it hasn’t, use ‘!=’, where ‘!’ is the logical negation operator, as you’ll see. Let’s take the following (useless) example:

#include <stdio.h>

int main()
{
  int var = 4;
  
  if (var == 4)
    printf("var is 4!\n");
  else
    printf("There's something amiss.\n");
  
  return 0;
}

Casting

In a nutshell, casting is forcing the compiler to forget about a variable’s type and treat as having another type that you supply. This isn’t done randomly, only between compatible types, and care is recommended when using casting. For example, let’s say we want to find out the ASCII value of ‘a’. The code could look like this:

#include <stdio.h>

int main()
{
  char c = 'a';
  printf("The ASCII value of 'a' is %d.\n", (int)c);
  
  return 0;
}

You will get the value 97, which is indeed the ASCII value of ‘a’. So, by using parentheses before and after the type you want to “impose” and all this before the variable’s name, you get casting. The example above works because a char is nothing more than a small int, so the types are compatible. Try casting the variable above to other types and note the results.

Increment and decrement operators

You’ve heard about C++ for sure. Well, it’s name suggests that it’s somehow more than C, because ‘++’ is an increment operator (adds 1 to the variable’s value), just as ‘–‘ is a decrement operator. These are unary operators and can be prefixed as well as postfixed. What does that mean? It means that you can write either ++c or c++, and the result may or may not be similar. The difference is that with ‘++’ prefixed, the value of the variable is first incremented by one, then used, and the other way around. We’ll show you a short example of when it matters and when it doesn’t.

#include <stdio.h>

int main()
{
  int x;
  int n = 10;
  int z;
  
  n++; /* n will be 11 now */
  ++n; /*ditto, prefix or postfix unimportant */
  
  x = n++; /* x will be 10 */
  z = ++n; /* z will be 11 */
  
  return 0;
}

But what if you want to increment/decrement with more than one? Simple, since c++ is the equivalent of c+=1. Replace 1 with whatever value you need and you’re set. These compound operators can also be used with any other binary arithmetic operators (e.g. *= or /=) and the bitwise operators as well, like ‘a &= b’.

Bitwise operators

In C you can do bitwise operations easily, but remember! They work and are to be used only with integer types, signed or unsigned. These operators are:

& - bitwise AND
| - bitwise OR
^ - XOR
<< - left shift
>> - right shift
-  - one's complement

Logical operators

We’ve already dealt with ‘!’, which negates any logical expression, but there are two very important logical operators (be careful not to mix them up with the bitwise ones): and and or, respectively. So, if I want to write in C something like “if variable 1 has value 2 and variable 2 has value 8”, I’ll write like this:

if (var1 == 2 && var2 == 8)
  ...

Here both conditions must evaluate as true for the instructions following if to execute. If either will do, or both, we replace ‘&&’ with ‘||’ (conjunction versus disjunction).

Other operators

People that have some C experience may have noticed the lack of some operators. Of course, and we’re aware of that, but what sense would it make to list the indirection operator while readers don’t know what a pointer is? So, the other operators, specific to other parts of C, will be dealt with in due time.

Conclusion

With the examples offered in this part, we’re certain you have enough to play a little and try various options. You know, the compiler won’t bite if you feed it wrong data, nor will the computer explode. And, like we said before, you can’t learn programming by reading books only. So get your keyboard and create something interesting.

Here is what you can expect next:



Comments and Discussions
Linux Forum