Overview of the C programming language

In this chapter we will look at the C programming language, how it compares to Fortran and Python, and why it can be useful for scientific computing.

A brief history of C

C was introduced in 1972 by Dennis Ritchie and Ken Thompson to aid in their development of the Unix operating system. The syntax was based on an earlier language, B, also written by Ritchie and Thompson.

The C language continued to evolve, and eventually was used to implement the entire Unix kernel, rather than just for writing programs to later be executed in a Unix environment. C and Unix continued to evolve together, much to the benefit of each. Though Unix is hardly used today, the Linux kernel is also written in C.

As C grew in popularity and utility, standards were put in place to formalize the language. This allowed others to write C compilers for whatever system they were on. The language underwent some dramatic changes in the early years, and you may still come across some older bits of code that may appear strange since they were written to conform to an older standard (compare the rigidity of Fortran 77 with the flexibility of Fortran 90).

The wiki page for C provides a nice overview of this history and compares the various standards.

Resources for C

A couple of nice resources for learning C are:

Conventions for the course:

For uniformity in the Fortran chapter we limited our discussion to the gfortran compiler and the Fortran 90/95 standard. For the same reason, we will limit ourselves to the gcc compiler and at a minimum, the C99 standard. We will look at how to select different standards below, though the default settings for gcc will work just fine.

Remark: The acronym gcc can be a bit confusing. GCC is the Gnu Compiler Collection, and gcc is the name of the C compiler within that collection. You should already have gcc present on your system from whenever you installed gfortran as both are part of that collection of compilers.

To verify that you have a working installation of gcc, try running the following from your terminal:

$ which gcc
/usr/bin/gcc
$ gcc --version
gcc (GCC) 10.2.0
...

The particular version of gcc doesn’t matter too much for this course, any reasonably up to date installation will be fine.

Comparing C to Fortran and Python:

We have already discussed Fortran and Python, so it will be helpful to orient ourselves with C by comparing it to these languages.

C and Fortran have many similarities. A few are:

  • Both are compiled languages

  • Both are imperative/procedural languages

  • Both support various floating point precisions

Indeed, many modern implementations of Fortran rely internally on C. There are also some key differences between the two:

  • Fortran is highly specialized for numerics, while C aims to be a general purpose systems programming language.

    • Common math operations are not part of the core C language, they need to be brought in and linked against at compile time (they are part of the standard library so this is easy to do).

  • Fortran hides much of what memory is doing, while C requires you to specifically state what you want.

    • This is partly what makes the multidimensional array support in Fortran so efficient. By hiding the underlying representation, and restricting what can be done, the compiler can often perform more aggressive optimizations.

    • Alternatively, it is often more comfortable to write more complex data structures in C. Understanding memory in C is a big hurdle facing newcomers to the language, but also provides some huge benefits.

  • By default Fortran uses 1-based indexing, and C uses 0-based indexing. Both can be changed, though Fortran makes this change a little more obvious.

  • Fortran is case-insensitive while C is case-sensitive.

  • Possibly the most important for mixed language programming: Fortran passes all arguments by reference. C, by default, passes arguments by value and requires you to specify when you want to pass by reference.

  • Many others…

C and Python also have many similarities and connections. Just a few are:

  • The most common Python interpreter is written in C.

  • Many modules for Python are written in C. This is a big part of what makes NumPy so efficient. Try importing numpy and calling type(np.sqrt).

  • Both use 0-based indexing by default.

Indeed, much of the design of Python is visibly influenced by C. Of course there are substantial differences between the two as well:

  • C is compiled while Python is interpreted

  • Python is dynamically typed, while C is (mostly) statically typed.

    • C could be described as weakly statically typed, though this level of detail is unimportant here.

  • Python almost never requires you to deal directly with memory

  • Python is object-oriented, while C is not.

    • We will look at one common design-pattern for C that mimics object-oriented behavior.

A simple example, and how to use gcc:

The gcc compiler, being part of the same collection as gfortran, shares many similarities to what we have already seen. Consider this small example program which evaluates the Pythagorean formula to give the hypotenuse length of a right triangle given the other two side lengths:

 1/* File: pythagoras.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate overall structure of C program as well as
 4 *          how to call gcc
 5 */
 6
 7#include <stdlib.h>
 8#include <stdio.h>
 9#include <math.h>
10
11double pythag(double a, double b)
12{
13  return sqrt(a*a + b*b);
14}
15
16int main(int argc, char **argv)
17{
18  /* Compute several hypo. lengths */
19  double a1 = 1.2,b1 = 3.3;
20  double c1 = pythag(a1,b1);
21  
22  /* Note that unlike Fortran 90, we can define variables anywhere */
23  double a2 = -3.0,b2 = 4.0;
24  double c2 = pythag(a2,b2);
25
26  // Similar to Python, we can do formatted output using specifiers
27  printf("Triangle with sides %f and %f has hypotenuse %f\n",a1,b1,c1);
28  printf("Triangle with sides %f and %f has hypotenuse %f\n",a2,b2,c2);
29
30  /* You can also nest function calls together */
31  double a3 = 5.0,b3 = 12.0;
32  printf("Triangle with sides %f and %f has hypotenuse %f\n",a3,b3,pythag(a3,b3));
33
34  /* We return 0 to tell the OS that everything ran normally */
35  return 0;
36}

Download this code

This can be compiled by calling:

gcc pythagoras.c -o pythagoras.ex -lm

Let’s take a moment to observe the structure of the program before discussing the compiler settings further. We can immediately note several differences between C and Fortran:

  • Every line has to end with a semi-colon, neither Python nor Fortran had line terminators

  • We need to explicitly include the IO and math headers to use the functions printf and sqrt respectively, and we need to link against the math library. These are not intrinsic to the language like they are in Fortran.

  • We can declare new variables anywhere, in any scope. Fortran disallows this, as do older versions of C.

  • We do not need to worry about floating point precision so much in C. Specifying type double forces the use of double precision, and all floating constants are generically of type double.

    • If you want single precision for some reason, use type float to declare variables, and append a trailing f to floating point constants. That is, double precision is the default behavior in C.

  • The main function is the entry point into the program. The curly braces on lines 17 and 36 are analogous to the program, end program pair in Fortran.

  • Curly braces denote blocks of code. Each block has its own scope.

  • The main function takes two arguments, and argv should look familiar. This is an array of strings that hold the various command line arguments passed to the program.

Now, let’s look at that compiler call more closely. We’ve already discussed the benefit of compiling with debug symbols present and warnings turned on. Indeed, the flags are mostly the same as for gfortran. Try compiling with:

gcc -g -Wall -Wextra -pedantic pythagoras.c -o pythagoras.ex -lm

What warnings do you observe and why? Many of the optimization flags and other controls carry over from our discussion on gfortran. We’ll see more as we progress through the chapter. If you want to specifically target the C90 standard (iso9899:1990) you can add the -std=c90 flag to your compile string:

gcc -std=c90 -g -Wall -Wextra -pedantic pythagoras.c -o pythagoras.ex -lm

which will fail, though that failure is easy to address. We’ll leave this unspecified and take the default setting throughout this chapter. It is good to know that this exists.

Exercise: Together let’s modify the above program to:

  • Print its own name every time it is called

  • Compute the hypotenuse length for user specified a and b from the command line

Primitive data types

Just like we had to specify the types of variables in Fortran (integer, real, real(kind=8), etc.), we need to specify types in C. A few of the built-in data types are listed here. Many of these types have further specializations.

There are several integer types that can each store different ranges of values. This is similar to the kind= behavior we saw in Fortran, only in C they are distinct types. The following code snippet shows a few:

int a = -3; /* Standard integer type, 32 bits, signed */
char c = 'a'; /* char is actually an integer, 8 bits, can store any ascii code */
unsigned long long int d = 18446744073709551000; /* 64 bit integer storing values 0 and above */

Note that characters in C are really just 8 bit integers. You generally won’t need to consider this for anything we do, but is good to know. Note also that the unsigned prefix generates an integer that can only store values greater than or equal to 0, but raises the ceiling on representable numbers. The modifier long increases the bit width, but its usage is a little strange. There are fixed width integer types that I suggest using instead if this is something that matters for your application.

The real number types are float and double. Their width depends slightly on the hardware, but for all relevant cases float will be 32 bits (equivalent to real(kind=4) in Fortran on most systems), and double will be 64 bits (equivalent to real(kind=8) in Fortran on most systems). Real constants are generally double width by default. A suffix can be used to override this:

float x = -1.3f;
double y = 2.3e-7; /* Note, C always uses 'e' for scientific notation */

That said, we will only ever use double in this class.

Other data types include booleans and complex floating point numbers. Both require you to include an additional header. For instance:

#include<stdbool.h>

bool b = true;

or,

#include<complex.h>

complex double z = 4.3e2 - 6.e-1*I;

Arithmetic and operators in C

Most of the arithmetic operators are what you would expect, as illustrated here:

double x = 3.14,y = 2.71,z = 0;
int n = 3, m = -5;

z = x - y;
z = x/y;
z = 15.3*(z + x);

n = m + n;

One notable point is that ** is not the exponentiation operator like it was in Fortran. To achieve this you need the pow function from math.h.

There are also integer specific operations:

int i = 0;

i = 13 % 3; /* Modulo operator, computes remainder */
i = 5 & 3;  /* bit-wise 'and' on integers */
i = 5 | 3;  /* bit-wise 'or' on integers */
i = 5 ^ 3;  /* bit-wise 'xor' on integers */
i = 15 >> 2; /* bit-wise shift right, similar for '<<' */

though, we’ll only really need the modulo operator.

Most operators can be combined with assignment:

double x = 3;
int j = 4;

x *= 2.3; /* Same as x = 2.3*x */

j += 3; /* Same as j = j + 3 */
j++; /* Same as j = j + 1 */

We’ll have more chances to look at the increment and decrement operators later. Another notable difference over Fortran is that /= is the divide-and-assign compound operator in C, while before it was the logical not equals operator. Speaking of, let’s look at the logical operators:

#include <stdbool.h>

int n = 5;
bool v = false;

v = (n == 5); /* Test equality, true here */
v = (n != 5); /* Test inequality, false here */
v = (n > 5);  /* Test less than, false here */
v = (n >= 5);  /* Test less than or equal to, true here */
v = (n > 2 && n < 7); /* logical and, both statements on either side of '&&' must be true to evaluate true */
v = (n != 5 || n < 3); /* Logical or, either statement can be true to evaluate to true */
v = !(n == 5); /* Logical negation */

Note that the parentheses on the right hand side of each statement are optional, though I find they add some readability when capturing a boolean into a variable. It doesn’t matter too much, since we’ll typically be using these comparisons directly inside a conditional statement.

Many of these operators also apply to pointers but we’ll cover that later.

Statements and control flow

Here we will look at some of the fundamental statements and constructs for controlling the flow of a program. Blocks of code are grouped together with curly braces. These operate in the same way that indentation did in Python, or the matching end tag did in Fortran.

If statements

If statements work in the same way as in Fortran and Python. Consider this small sample code:

 1/* File: ifelse.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate if-else if-else conditional blocks
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9int main()
10{
11  int n = 5, m = -3;
12  /* If statements can be used alone */
13  if (m < n) {
14    printf("m is smaller than n\n");
15  }
16
17  /* If there is only one statement after the if, then the braces are optional */
18  if (m < n)
19    printf("m is smaller than n\n");
20  
21  /* They can be chained with else statments */
22  if (m == n) {
23    printf("m is equal to n\n");
24  } else {
25    printf("m is not equal to n\n");
26  }
27
28  /* An arbitrary number of else if statements can be included */
29  if (m == 2) {
30    printf("m is equal to 2\n");
31  } else if (m == 6 || m == 7) {
32    printf("m is equal to 6 or 7\n");
33  } else if (m == -3) {
34    printf("m is equal to -3\n");
35  } else {
36    printf("m is not equal to any tested value\n");
37  }
38
39  /* The spacing and layout of the braces doesn't matter too much */
40  /* The last example above could be written as: */
41  if (m == 2)
42    {
43      printf("m is equal to 2\n");
44    }
45  else if (m == 6)
46    {
47      printf("m is equal to 6\n");
48    }
49  else if (m == -3)
50    {
51      printf("m is equal to -3\n");
52    }
53  else
54    {
55      printf("m is not equal to any tested value\n");
56    }
57
58  return 0;
59}

Download this code

This can be compiled by calling:

gcc ifelse.c -o ifelse.ex

Note that the braces can be laid out in different ways. As long as you pick one style, and stick with it consistently, everything will be fine. I personally like the former style, which will be used throughout these notes.

For loops

For loops have the same purpose as for loops in Python, and do loops in Fortran. They are primarily useful for looping for a predetermined number of iterations. Even for tasks that require an indefinite number of iterations, a for loop can be a good choice to enforce a cap on the iterations. The general syntax of a for loop is:

for(/*initialization*/ ; /*exit condition*/ ; /*iteration*/) {
  /* Whatever is to be done each time through the loop */
}

The for loop takes three fields separated by semi-colons. They are:

  1. Initialization: Any setup that should be called once before the loop is executed. Usually this sets the loop variable to zero.

  2. Exit condition: This is a conditional statement, that when false, terminates the loop. This is usually a comparison of the loop variable to some limit.

  3. Iteration statement: This is executed after the loop body is executed, each time through the loop. This usually increments the loop variable.

This is easier to see by demonstration. Consider the following code that sets up and runs through several for loops:

 1/* File: forloops.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate usage of for loops
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9int main()
10{
11  int N = 5;
12  /* Basic version of a for loop */
13  for (int i=0; i<N; i++) {
14    printf("First loop, iteration %d\n",i);
15  }
16
17  /* Question: why would the next line fail? */
18  /* printf("i = %d\n", i); */
19
20  /* For loop running backwards */
21  for (int i=N-1; i>=0; i--) {
22    printf("Second loop, iteration %d\n",i);
23  }
24
25  /* For loop that increments in larger steps */
26  for (int i=0; i<2*N; i+=2) {
27    printf("Third loop, iteration %d\n",i);
28  }
29
30  /* Some fields can be blank (compare this with a while loop) */
31  for (int i=0; ; i++) {
32    printf("Fourth loop, iteration %d\n",i);
33    /* Don't forget to terminate! */
34    if (i == N-1) {
35      break;
36    }
37  }
38
39  /* The final field doesn't need to be an increment operation */
40  for (int i=2; i<=128; i*=2) {
41    printf("Fifth loop, iteration %d\n",i);
42  }
43
44  /* The body will usually be more interesting than just a print statement */
45  int sum = 0;
46  for (int i=2; i<=128; i*=2) {
47    sum += i;
48  }
49  printf("Sum = %d\n",sum);
50  
51  return 0;
52}

Download this code

This can be compiled by calling:

gcc forloops.c -o forloops.ex

Let’s make some observations about this code:

  • The print statements look like the formatted printing from Python, more on this later though

  • Each of the three fields are optional, though typically all are used

  • If a field is not used, like the conditional in the fourth example, then a different type of loop is more likely to be useful.

  • The sum in the last list is defined outside, why? The print statement on line 18 fails if included, why?

The basic fields in the loop can also be made more complex, consider changing the last loop to be:

for(int i=2, sum=0; i<=128; i*=2) {
  sum += i;
  printf("Running sum = %d\n",sum);
}

While and do-while loops

While loops and do while loops behave in a different way than for loops do. They are primarily used in cases where the number of iterations is unknown ahead of time. The structure of a while loop is simple:

while(/*conditional statement*/) {
  /* Loop body */
}

The conditional statement is checked before each pass through the loop body, and when this statement false the loop is terminating and the program continues from after the closing brace. Do-while loops have a similar structure:

do {
  /* Loop body */
} while(/*conditional statement*/);

Note the trailing semi-colon after the conditional statement. These are demonstrated in the following example. Can you spot the difference between the while loop and the do-while loop?

 1/* File: whileloops.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate usage of while and do-while loops
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9int main()
10{
11  int N = 5, i=0;
12  /* Basic version of a while loop, compare to for-loop #1 */
13  while (i < N) {
14    printf("First loop, iteration %d\n",i);
15    i++;
16  }
17
18  /* This time this works, why? */
19  printf("After loop i = %d\n",i);
20
21  /* What if we try again? */
22  while (i < N) {
23    printf("Second loop, iteration %d\n",i);
24    i++;
25  }
26  /* Why was there no output? */
27
28  /* do-while loops are similar */
29  int ctr = 5;
30  do {
31    printf("Third loop, ctr = %d\n",ctr);
32    ctr--;
33  } while (ctr > 0);
34
35  /* Can you spot why this is different from above? */
36  do {
37    printf("Fourth loop, ctr = %d\n",ctr);
38    ctr--;
39  } while (ctr > 0);
40  
41  return 0;
42}

Download this code

This can be compiled by calling:

gcc whileloops.c -o whileloops.ex

A brief aside on printf

In the above programs we’ve been using printf to write to the screen. The f suffix denotes this as the formatted print function. The first argument is a string which will get printed. If this string contains special sequences called format specifiers, these entries will get replaced by the arguments following the initial string. If the string has 3 format specifiers, then there should be 3 additional arguments after the string.

There are quite a few format specifiers, which all have the notation of a % sign followed by a character code. A few relevant ones are:

  • %c Character

  • %u Unsigned integer

  • %d Signed integer

  • %e Double or float in scientific notation

  • %f Double or float

  • %s String (null-terminated)

They can also be combined with fixed width printing. See the following example:

 1/* File: printstatements.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate formatted printing
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9int main()
10{
11  unsigned int ui = 5;
12  int si = -3;
13  double faultyPi = 3.14,eps = 1.e-14;
14
15  /* Print all of these out */
16  printf("ui=%u, si=%d, faultyPi=%f, eps=%e\n",ui,si,faultyPi,eps);
17  
18  /* Print again with fixed width */
19  printf("ui=%2u, si=%3d, faultyPi=%2.3f, eps=%1.3e\n",ui,si,faultyPi,eps);
20  
21  /* Print again with fixed width and pad by zeros */
22  printf("ui=%02u, si=%03d\n",ui,si);
23
24  return 0;
25}

Download this code

This can be compiled by calling:

gcc printstatements.c -o printstatements.ex

Formatted input can be done by using the scanf function. It behaves in much the same way that printf does, and uses the same format specifiers. The arguments after the first string will now be variables to store the input into. We’ll talk about this again after we discuss pointers.

Functions

In the first sample program we defined the custom function pythag. functions in C contain the behavior of both functions and subroutines in Fortran. Let’s look more closely at the syntax there. Generically, the layout of a function declaration is:

<return type> somefunc(<arg1 type> <arg1>, <arg2 type> <arg2>, ... )
{
  /* Function body */
  return <some value matching the return type>;
}

For instance, the previous example:

double pythag(double a, double b)
{
  return sqrt(a*a + b*b);
}

defines a function named pythag that returns the type double, and takes in two arguments of type double named a and b.

The return and argument types can be any of the primitive types discussed above, or any derived type that you can come up with (more about that later). If your function does not return anything, you can use the special type void. Consider a small wrapper on printf that we can use for the triangles we had in the above sample:

void printTriangle(double a, double b)
{
  double c = pythag(a,b);
  printf("Triangle with sides %f and %f has hypotenuse %f\n",a,b,c);
}

Note also that the variable c exists only within the function printTriangle, and is not accessible from outside the function body. Similarly, this variable will not collide with any other variables named c (as long as they aren’t defined globally).

Functions can of course take any number of arguments, of any types. The argument list can also be left blank if your function takes no arguments.

void sayHello()
{
  printf("Hello!\n");
}

void greetRepeat(const char *name, int n)
{
  for(int i=0; i<n; i++) {
    printf("Hello %s!\n",name);
  }
}

Note that functions in C can not be overloaded. This means we are not allowed to name the second function sayHello. Finally, there are qualifiers that can be applied to function declarations, though we’ll discuss these later.

Arrays (static and VLA)

We’ve seen repeatedly how important arrays are for scientific computing. C makes some distinctions about different arrays depending on how they are allocated in the program. The simplest case is a statically allocated array, where the number of elements is constant and known at compile time. In this case, the declaration looks like this:

int a[10]; /* Array of 10 integers */
double b[3] = {1.2, -2.3, 4.e-12}; /* Array of 3 doubles, initialized to the given values */
double c[] = {1.2, -2.3, 4.e-12}; /* The [3] can be written as [] since the number of entries is unambiguous */
const unsigned int sz = 7;
char d[sz]; /* Array of characters of length 7 */
double A[3][4]; /* Array of 12 doubles, with 3 rows and 4 columns */

Consider this example code:

 1/* File: arrays.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate statically allocated arrays
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9int main()
10{
11  /* Array of 4 doubles, uninitialized */
12  double b[4];
13  
14  /* Array of 3 doubles, set to initial values */
15  double x[] = {3.2, 4.5, -6.2};
16
17  /* 4x3 array of doubles */
18  double A[4][3];
19
20  /* Fill A with something interesting */
21  for (int i=0; i<4; i++) {
22    for (int j=0; j<3; j++) {
23      A[i][j] = ((double) i*j+1); /* Typecast not needed, but adds clarity */
24    }
25  }
26  
27  /* Store A*x into b, interpreted as matrix-vector product */
28  for (int i=0; i<4; i++) {
29    b[i] = 0;
30    for (int j=0; j<3; j++) {
31      b[i] += A[i][j]*x[j];
32    }
33  }
34  
35  /* Print out result */
36  printf("b = (%1.2f,%1.2f,%1.2f,%1.2f)\n",b[0],b[1],b[2],b[3]);
37  
38  return 0;
39}

Download this code

This can be compiled by calling:

gcc arrays.c -o arrays.ex

Observe:

  • Accessing elements uses square brackets and zero based indexing

  • Rank 2 (and above) arrays utilize another set of brackets for each index. This is not the same as in Python or Fortran

  • Line 23 includes a typecast. This turns the integer expression in the outer parentheses into a double expression. This is automatic, but annotating the conversion can be helpful to other readers

Why does the method for allocation really matter? Arrays in C are placed in memory contiguously, so the compiler needs to know how to generate enough space for the array to be stored at.

Note further that contiguous storage requires that higher rank arrays are really just long rank 1 arrays as far as the memory is concerned. This has one notable side-effect, there isn’t just one way to flatten a high rank array. Again, we should contrast this with Fortran:

  • C stores rank 2 arrays in row-major format. This means that in memory this array is flattened by separating the rows, and placing them next to each other.

  • Fortran stores rank 2 arrays in column-major format. Columns are separated from one another, and stacked into one long representation.

  • For higher rank arrays, the last index varies the fastest in C, and the first index varies fastest in Fortran.

    • What does this mean for performance? If you need to access all entries, what order should you use?

If you recall from the Fortran chapter we could write subroutines for arbitrary size arrays, as long as we knew their rank. Similarly, we could declare arrays with sizes dependent on some integer in the program. We noted that this was not allowed in Fortran 77. In C we can do the same thing with variable length arrays, and like Fortran, this requires using a newer standard (C99 or above). This is demonstrated in the following example:

 1/* File: vlarrays.c
 2 * Author: Ian May
 3 * Purpose: Demonstrate variable length arrays as function arguments
 4 */
 5
 6#include <stdlib.h>
 7#include <stdio.h>
 8
 9void InitMatrix(int m, int n, double A[m][n])
10{
11  for (int i=0; i<m; i++) {
12    for (int j=0; j<n; j++) {
13      A[i][j] = ((double) i*j+1);
14    }
15  }
16}
17
18void MatVecMult(int m, int n, double A[m][n], double x[n], double b[m])
19{
20  for (int i=0; i<m; i++) {
21    b[i] = 0;
22    for (int j=0; j<n; j++) {
23      b[i] += A[i][j]*x[j];
24    }
25  }
26}
27
28int main()
29{
30  /* Array of 4 doubles, uninitialized */
31  double b[4];
32  
33  /* Array of 3 doubles, set to initial values */
34  double x[] = {3.2, 4.5, -6.2};
35  
36  /* 4x3 array of doubles */
37  double A[4][3];
38  
39  /* Fill A with something interesting */
40  InitMatrix(4, 3, A);
41
42  /* Store A*x into b, interpreted as matrix-vector product */
43  MatVecMult(4,3,A,x,b);
44  
45  /* Print out result */
46  printf("b = (%1.2f,%1.2f,%1.2f,%1.2f)\n",b[0],b[1],b[2],b[3]);
47  
48  return 0;
49}

Download this code

This can be compiled by calling:

gcc vlarrays.c -o vlarrays.ex

Observe:

  • Like in Fortran we need to specify the rank (number of indices) of the array inside the argument

  • Unlike Fortran, we still need to pass the size of the array in each dimension

This second point can be confusing, why are we calling this a variable length array when the size has to be specified? This is because m and n can change between calls to the function. This will become much more clear when we talk about dynamic memory and array flattening. However, before that we need to understand pointers. See the reference page on array declaration for more information.

Exercise: Change the main file to read an integer as a command line argument, and use that in place of 4 when declaring the sizes of A and b.