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:
The GNU C Reference Manual, which these notes very loosely follow
C language reference, I still use this fairly heavily.
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 callingtype(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}
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
andsqrt
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 trailingf
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 theprogram
,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
andb
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}
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:
Initialization: Any setup that should be called once before the loop is executed. Usually this sets the loop variable to zero.
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.
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}
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}
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}
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}
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}
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
.