C the Arrays

C vs C++: Arrays

As we did back in Lecture 16, we are now pausing to see how the C language handles arrays. And as before, you will see that many differences are superficial and what you've learned for C++ in this class nicely applies to C.

As a reminder of the differences we've already covered:

Today we will look at arrays. The short description is that C requires you to be more explicit with how much memory you need for an array, but after that, pointers and array usage is the same.

Allocating Memory for an Array: malloc

NOTE: you need to now #include <stdlib.h> for malloc and calloc.

In C++, we've learned that memory is allocated with the keyword new. For example, to create an array of 5 ints:

int* numbers = new int[5];   // C++ code

In C, we instead use the keywords malloc or calloc. They're almost, but not exactly, the same. The prototype for malloc is:

void* malloc(int);

The parameter of type int needs to be the number of bytes that you want for your array from the operating system. So, when requesting space for an array of 5 ints, for example, to arrive at the correct number of bytes to request, you need to know not only the 5, but also the number of bytes required by a single int. Most systems represent an int with 4 bytes, but not always, and embedded systems can be different from platforms. To know how many bytes your type needs, use the sizeof() function. For example:

malloc( 5*sizeof(int) );

calls sizeof(int), which returns the number of bytes needed to store a single int. This is then multiplied by 5 (because you need room for 5 of them), the result of which is finally the argument to malloc.

Because malloc just accepts a number, it has no knowledge of the type you're going to store within that space, so it doesn't know whether to return a int* or a char* or what. So, it just returns a void*, which can be cast to any of those types. So, for our array of 5 ints, we finally end up at:

int* intArr = malloc( 5*sizeof(int) );

Say we instead want 12 doubles, then it looks like this:

double* doubles = malloc( 12*sizeof(double) );

You can view this as a simple syntax change, but there is a little more going on. In C++, the new keyword figures out how many bytes you need based on the primitive type. In C, you have to compute that yourself in the code (with the handy sizeof() function). Here is the side-by-side difference:

C++C
int* nums = new int[12];
int* nums = malloc( 12*sizeof(int) );

calloc

Our other option for memory allocation is calloc, which makes this "I need to know two things, the size of the type and the number of elements I'm story" thing explicit. In calloc, our "array of 5 ints" and "array of 12 doubles" looks like this:

int* intArr = calloc( 5, sizeof(int) );
double* doubles = calloc( 12, sizeof(double) );

You'll notice that it takes two arguments, while malloc just requires you to multiply them together yourself. The other significant difference is that unlike new or malloc, calloc not only allocates the memory, it also zeroes it out(!!)! No random garbage in your arrays! But, it's somewhat slower, since it has to iterate over all the memory and zero it out.

The choice between malloc and calloc is largely a personal preference between the two, as long as you're knowledgeable about these differences.

When using malloc or calloc, be sure to #include <stdlib.h>, or you'll get a warning about the return type of your allocation function.

Filling an Array with scanf

Remember that scanf requires a pointer to know which memory address will be filled. For example, reading an int:

int N;
scanf("%d", &N);
Nothing changes with arrays, but it might not yet be intuitive to you, so let's be explicit. An array cell is an lvalue which makes it no different from a standard primitive type. If you want to read an array of 10 ints from standard input, it looks like you'd expect:
int* nums = malloc( 10*sizeof(int) );
for( int i = 0; i < 10; i++ )
   scanf("%d", &nums[i]);   // we just need the & to get the address of the array cell

free

Remember to delete your memory allocation when you're finished with your arrays!

The C equivalent of delete is the function free:

free(intArr);
free(doubles);

strings

Remember how we lost our great string type from C++?

It's not so bad. Strings in C are instead explicit arrays of chars. There is a small catch, though. The final char of your array must be a null character (0 on your ASCII table, first in your heart). So, if you were to encode the word "hi" as a C string, you would need an array of size three:

char* hiStr = malloc( 3*sizeof(char) );
hiStr[0] = 'h';
hiStr[1] = 'i';
hiStr[2] = 0; //alternatively, the character '\0'

Alternatively and wonderfully, you can also do this:

char* hiStr = "hi";

The null char is still there, just unseen in the code. This second case builds the array in the stack, rather than the heap - this means that this second example does not need to be freed, while the first does. In general, we're avoiding this in our class (it's called "static allocation"), but for C strings it's too common to step around. You'll also see:

char aString[20];

which builds an array of 20 chars in the stack, pointed to by a char* aString. Again, you can technically build arrays in the stack like this for any type but it's exceedingly common with strings, which tend to be short, and so people put them in the stack, so they don't have to free them.

Once you've allocated space for some chars, your pointer can be used with scanf to read in strings from the user:

char aString[20];
scanf("%s",aString);

or

char* aString = malloc( 20*sizeof(char) );
scanf("%s",aString);

As with cin, the string will be read in until whitespace - here, you have to be careful to have allocated enough space for the string you'll be reading in!

Finally, the above is all about creating and reading strings. But what about helpful functions? Make sure you #include <string.h>. This library provides a wide variety of useful string functions.

Reading from files

Again, very superficial differences. First, you make a pointer to a FILE using fopen, which accepts two C strings as arguments, the first of which is a filename, and the second is a mode (detailed here). After doing this, you can use fscanf or fprintf just like you would scanf and printf. So, to open a file and read an integer:

FILE* fin = fopen("someFile.txt","r");
int theInt;
fscanf(fin, "%d", &theInt);

Problems

  1. Write a C program solution to read n numbers from a user and print them in reverse:
    $ ./avg
    How many? 5
    5 3 9 2 3
    3 2 9 3 5
  2. Write a C program solution that reads the file format numbers.txt into an array. Then compute the average and find the max. Yes, this can be done without an array, but use an array for practice:
    $ ./fileread
    Average is 34.850000
    Max is 94