Pulsars and neutron stars/Introduction to C for pulsar astronomers
Introduction
[edit | edit source]Many of the pulsar-based software packages are written in C (such as tempo2 and sigproc).
C programs are first written as a text file (or set of text files) using a text editor (there are specific editors for writing programs, but it is also possible to use emacs or vi). The programs then need to be compiled before they can be run. Any changes to the programs require re-compiling.
You can check that you have gcc installed, and the version number using
$ gcc -v
A basic program
[edit | edit source]/*
Anything between the * symbols will be a comment
This can go over multiple lines.
*/
// You can also comment on a single line like this
#include <stdio.h> // This is a library linked to input and output. This should always be included at the top of the program
#include <math.h> // This is a library related to math functions
// All C programs have to have a function called main.
int main()
{ // Everything between this { and the } will be part of the main function
printf("Hello everyone!\n"); // The \n is a new line. The ; symbol states that this is the end of a command
}
Save the code as test.c. Compile using
$ gcc test.c
This produces an output file called "a.out". To run
$ ./a.out
You can also use:
$ gcc -o test test.c
and that makes the output file called "test" which can be run using
$ ./test
Some variables
[edit | edit source]#include <stdio.h>
#include <math.h>
int main()
{
int x; // Variables must (in C) be defined at the top of a function. Here we say that x is an integer. Note that by default this is not
// set to any particular value. It could be anything
float y; // This is a floating point number (i.e., a number with a decimal point - e.g., 1.23455)
float z;
// We can set these values
x = 5;
y = 1.2345;
z = y*2+x; // Maths is quite straightforward. You can use the +,-,*,/ symbols. Note that there's no default C command for "to the power".
printf("Hello everyone!\n");
printf("x = %d, y = %f and z = %f\n",x,y,z); // %d means print an integer and %f is used to print a float
}
Functions
[edit | edit source]The programmer can create their own functions. These may be functions that will only be used in the particular program currently being written or may be used more often in lots of different programs.
A function can simply package up a bit of code that may be run multiple times.
#include <stdio.h>
#include <math.h>
// Declare the function that we're going to create
void writeHeaderInfo(); // The void states that this function will not return any results
int main()
{
writeHeaderInfo(); // This is a simple function to print out some information about the program. A function is called by giving its name and the brackets.
}
// We can now create the function. The next line should be identical to the declaration of the function at the top of the code (apart from the semi-colon)
void writeHeaderInfo()
{
printf("Hello World. This is coming from the writeHeaderInfo function\n");
}
Note that functions cannot access data from outside the function (unless you use global variables - don't). So, if you define a value in one function, it cannot be directly used in another function.
#include <stdio.h>
#include <math.h>
// Declare the function that we're going to create
void writeHeaderInfo();
int main()
{
int x = 5;
printf("Here x is %d\n",x);
writeHeaderInfo();
printf("Back in main and x is %d\n",x);
}
void writeHeaderInfo()
{
int x = 10; // Note that you can define the value of a number at the same time as stating it is e.g., an "int"
printf("Here x = %d\n",x);
}
In the above example, x = 5 in the main function and 10 in the new function.
It is possible to make a function return a value. Note that C functions can only (easily) return a single value.
#include <stdio.h>
#include <math.h>
// Now we're defining the function to return an integer value
int writeHeaderInfo();
int main()
{
int x = 5;
printf("Here x is %d\n",x);
x = writeHeaderInfo(); // Note that we're now setting x to be the result from the function
printf("Back in main and x is %d\n",x);
}
// Note we have to change the void to an int here as well
int writeHeaderInfo()
{
int x = 10; // Note that you can define the value of a number at the same time as stating it is e.g., an "int"
printf("Here x = %d\n",x);
return x; // This return statement says what value will be returned from this function
}
This should display that x is 5, then 10 and then 10 again.
It is also useful to pass values to a function:
#include <stdio.h>
#include <math.h>
// Now we're defining the function to return a floating point value
float writeHeaderInfo(int a,float b,float c);
int main()
{
int x = 5;
float y = 1.2345;
float z = pow(x,2)+sqrt(y); // These maths functions require "math.h" to be defined
float retVal; // Variable names do not have to be a single character (but they cannot include spaces)
printf("Here x is %d\n",x);
retVal = writeHeaderInfo(x,y,z); // We're passing an integer (x) and two floats (y and z) to the function
printf("Back in main and x is %d\n",x);
printf("The returned value is %f\n",retVal);
}
// We're going to pass into this function an integer and two floats. Within this function they'll be called
// a, b and c respectively
float writeHeaderInfo(int a,float b,float c)
{
float x;
x = a+b*c;
return x;
}
Loops
[edit | edit source]Programs in C basically start at the top and then go to the bottom of each function. There are a few ways to repeat parts of the function. We can use "for", "while" or "do" loops as follows.
#include <stdio.h>
#include <math.h>
int main()
{
int x;
for (x=10;x<20;x=x+1)
{
printf("x = %d\n",x);
}
}
This should print out x = 10, x = 11 ... x = 19 on the screen. First x is set (x=10). It then checks whether x < 20, it is! Then it carries out all the statements between the curly-brackets. At the end bracket it adds one to x (x = x + 1) and then re-does the check to see if x < 20.
There are a few simplifications. Instead of writing x = x + 1; you can write
x++;
For x = x + 2, you can write:
x+=2;
Another simplification is that curly-brackets are not needed in loops if the loop only has one command:
for (x=10;x<20;x++)
printf("x = %d\n",x);
It is also possible to use a while loop:
x=10;
while (x < 20)
{
printf("x = %d\n",x);
x++;
}
Note that a while loop will do the check (x < 20) before carrying out any of the commands inside the loop. In contrast a "do" loop does the check at the end:
x=10;
do
{
printf("x = %d\n",x);
x++;
} while (x < 20);
Conditional statements
[edit | edit source]Often some commands should only be run in some cases. For instance:
#include <stdio.h>
#include <math.h>
int main()
{
int x;
for (x=10;x<20;x++)
{
if (x < 15)
{
printf("x = %d. This is less than 15\n",x);
}
else if (x > 17)
{
printf("x = %d. This is greater than 17\n",x);
}
else
{
printf("x is not less than 15 and not greater than 17\n");
}
}
}
Again, this can be simplified to:
#include <stdio.h>
#include <math.h>
int main()
{
int x;
for (x=10;x<20;x++)
{
if (x < 15)
printf("x = %d. This is less than 15\n",x);
else if (x > 17)
printf("x = %d. This is greater than 17\n",x);
else
printf("x is not less than 15 and not greater than 17\n");
}
}
Note:
- < less than
- <= less than or equal to
- > greater than
- >= greater than or equal to
- == equals
- != does not equal
Arrays
[edit | edit source]Instead of creating a variable that contains a single number, it is possible to create an array of values (or characters). Let's allocate 5 numbers:
int main()
{
int x; // This is a single number
int y[5]; // This in an array that represents 5 numbers. Note the use of square brackets here.
int i;
x = 10;
y[0] = 1; // Note that the first element is y[0] not y[1]
y[1] = 2;
y[2] = 5;
y[3] = 10;
y[4] = 12;
// Now let's print them out using a loop
for (i=0;i<5;i++)
printf("y[%d] = %d\n",i,y[i]);
}
We can set the values in a loop:
for (i=0;i<5;i++)
y[i] = i*2;
Note that the compiler will not check whether you are actually using the allocated memory. In the above example you allocated an array of size 5, but you can actually write y[100] = 10. This will probably cause a segmentation fault when you run the code, but may also crash the computer!
Strings
[edit | edit source]Strings are words or phrases. In C they are actually arrays of characters. Characters are stored as "char":
int main()
{
char c;
c = 'x'; // Set c to the character "x" Note the single quotes.
printf("c = %c. As an integer this character is %d\n",c,c);
}
The integer value for each character can be found in tables of ASCII characters. A capital "A" is 65, "B" = 66, etc.
To make a string we need an array of characters
int main()
{
char c[50];
c[0] = 'h';
c[1] = 'e';
c[2] = 'l';
c[3] = 'l';
c[4] = 'o';
printf("My string is %s\n",c); // Note the use of %s to represent a string and note the use of just c instead of e.g., c[0]
}
Notice that C doesn't know where the end of the string actually is and it may be producing junk on your screen. You can use the end of string character "\0" to fix this:
int main()
{
char c[50];
c[0] = 'h';
c[1] = 'e';
c[2] = 'l';
c[3] = 'l';
c[4] = 'o';
c[5] = '\0';
printf("My string is %s\n",c);
}
Now the string should stop at the correct position. Creating strings this way is clearly not very practical. Instead you can use a "C" library that provides functions for processing strings.
#include <stdio.h>
#include <math.h>
#include <string.h> // Note the inclusion here of string.h
int main()
{
char c[50];
strcpy(c,"Hello!"); // strcpy = copy a string into the variable. Note the double quotes.
printf("My string is %s\n",c);
strcat(c," a bit more"); // strcat will add another string to the end of your current string
printf("Now c is %s\n",c); // Note that strcat doesn't check to see if you run off the end of your string
if (strcmp(c,"boo")==0) // strcmp is a string comparison to see if two strings are the same or not. If they are the function returns 0.
printf("c is boo\n");
else
printf("c is not boo\n");
}
also take a look at strcasecmp, strlen and strstr. Note that in C you cannot write:
c = "fred"; // This does not work
instead you have to use strcpy.
Getting information into the program
[edit | edit source]Currently you can input data directly in the program:
x = 5;
However, often the user wishes to input values or the information should be obtained from the command-line or from a file.
Getting input from the keyboard
[edit | edit source]int main()
{
int x;
printf("Please enter an integer number ");
scanf("%d",&x); // Note the use of an "&" here. This will be described later.
// %d is for an integer
// %f is for a float
// %lf is for a double (l = small L)
printf("You entered %d\n",x);
}
For a string, use:
int main()
{
char s[256];
printf("Please enter a string ");
scanf("%s",s); // Note that we're not using an & symbol here - because s is an array
printf("You entered %s\n",s);
}
Note that there is a danger in using scanf. It doesn't check to make sure that you actually are typing in e.g., an integer. You could ask "what is you name?" and the user types "hello" or "-3.23" or anything else. This may cause the program to crash or give incorrect results. When reading a string, you need to define the maximum number of characters (256 in the above example). C doesn't check to make sure that the user does actually write fewer than this many characters. If more, then the program may crash. There are other routines (e.g., "gets", "fgets" etc. that can help with this).
Command line arguments
[edit | edit source]When you run your program from the command line you normally type something like
> ./myprog
You can also pass command line arguments such as:
> ./myprog -f try.dat -k 2 -input data.input -mult 3.234
To read these into the code, use the following
int main(int argc,char *argv[]) // This * and [] will be explained later - see section on "pointers"
{ // In the above, main() is now a function that takes in two inputs. argc contains the number of command line arguments
int i;
printf("The number of command line arguments is %d\n",argc);
for (i=0;i<argc;i++)
printf("The argument number %d is %s\n",i,argv[i]);
}
Often we need to read an argument in a variable. For instance, let's run
> ./myprog -k 2 -n 1.234 -title "Hello everyone"
To determine k, n and title we need something like the following:
int main(int argc,char *argv[]) // This * and [] will be explained later - see section on "pointers"
{ // In the above, main() is now a function that takes in two inputs. argc contains the number of command line arguments
int kval;
float nval;
char title[128];
printf("The number of command line arguments is %d\n",argc);
for (i=1;i<argc;i++)
{
if (strcmp(argv[i],"-k")==0)
{
// kval = argv[i+1]; // I want to write this, but I cannot do this because kval is an integer and argv[i+1] is a string. Instead ...
sscanf(argv[++i],"%d",&kval); // Note that sscanf is like "scanf", but reads from a string
// Note that ++i means "add one to i before using it". i++ means "use i and then add 1"
}
else if (strcmp(argv[i],"-n")==0)
sscanf(argv[++i],"%f",&nval);
else if (strcmp(argv[i],"-title")==0)
strcpy(title,argv[++i]); // Notice use of strcpy here
else
printf("Unknown command line argument: %s\n",argv[i]);
printf("Our results are k = %d, n = %f, title = %s\n",kval,nval,title);
}
}
Reading and writing files
[edit | edit source]Let's assume that we have a file that contains two rows of numbers. We wish to read in those numbers, add them together and produce a new file containing the sum. Let the input file be called in.dat and have the following content:
1 2
3 4
5 6
7 8
9 10
We can read it in and print it to the screen as
int main()
{
FILE *fin; // This is known as a "file pointer". Note that FILE is written in capital letters.
// commonly we use "fin" for file in and "fout" for file out, but you can use anything
float x,y; // Note that we can define two values on the same line
fin = fopen("in.dat","r"); // The first argument is the file name. The second "r" means that we want to "r"ead the file
while (!feof(fin)) // This is a while loop that continues until we are not (!) at the end of the file (feof)
{
if (fscanf(fin,"%f %f",&x,&y)==2) // fscanf is like "scanf", but reading from a file. The function returns the number
// of values successfully read from the file
{
printf("Loaded x = %f, y = %f x+y = %f\n",x,y,x+y);
}
}
fclose(fin); // This will close the file
}
Now let's write the addition out to a new file
int main()
{
FILE *fin;
FILE *fout;
float x,y,z;
fin = fopen("in.dat","r");
fout = fopen("out.dat","w"); // "w" is for writing a file. "a" is for appending to a file
while (!feof(fin))
{
if (fscanf(fin,"%f %f",&x,&y)==2)
{
printf("Loaded x = %f, y = %f x+y = %f\n",x,y,x+y);
z = x+y;
fprintf(fout,"x + y = %f\n",z); // Note that this is exactly the same as a printf statement, but it writes the output to a file
}
}
fclose(fin);
fclose(fout); // Close the file
}
Pointers
[edit | edit source]Pointers can be very confusing. Writing:
int x = 5;
allocates part of memory, labels it as "x" and stores the number 5. This can be accessed or modified using the label, e.g.,
printf("x = %d\n",x);
x = x + 2;
You can also determine the actual memory location using the ampersand (&) operator:
int x = 5;
printf("x = %d\n",x);
printf("The address of x in memory is %x\n",&x);
Passing the memory location allows you to modify a bit of memory in another function
void myFunc1(int y);
void myFunc2(int *y);
int main()
{
int x = 5;
printf("x = %d\n",x);
myFunc1(x);
printf("Here x is %d\n",x);
myFunc2(&x);
printf("Here x = is %d\n",x);
}
void myFunc1(int y)
{
y = 20;
}
void myFunc2(int *y) // Notice that we pass the variable using the & symbol, but here we use a "*" symbol - this is a pointer to a part of memory
{
*y = 20; // Here y is a pointer to a part of memory. We use the "*" to say that we want to change the value stored in the memory pointed to by "y".
}
In summary, a pointer is a variable (e.g., it must be declared and it has a label etc.) whose value is the memory address of another variable. To declare a pointer use the * symbol. To obtain the address of a normal variable use the & symbol.
int x=5; // This is not a pointer
int *y; // This is a pointer
y = &x; // This is valid.
printf("The address of y is %x. The value in the memory at this address is %d",y,*y); // Note that the * here is used to return the value of the variable located at the address.
Pointers can be very powerful and can make your code very fast. Here is the use of a pointer to print out the elements of an array:
int x[10]; // This is an array of 10 elements
int i;
// Let's set up this array
for (i=0;i<10;i++)
x[i] = i*2;
// &x[0] is equivalent to x. So note that x is actually a pointer here.
// We can print out the elements using:
for (i=0;i<10;i++)
printf("x[%d] = %d\n",i,x[i]);
// We can also use
for (i=0;i<10;i++)
printf("x[%d] = %d\n",i,*(x+i));
Structures
[edit | edit source]Structures provide the means to move away from the simple variable types (int, float, double) defined by C. For instance, a pulsar observation may have an observation time, a frequency, a pre-fit residual, a post-fit residual, etc. etc. This can be set up using a structure as follows:
typedef struct psrObs { // Define a new type of variable called a psrObs. Note that this is before the main() function
double toa;
double freq;
char telID[16];
} psrObs;
int main()
{
psrObs obs1;
psrObs obs2;
obs1.toa = 53123.123; // Notice the use of the "dot" here to set part of the structure
obs1.freq = 1400.0;
strcpy(obs1.telID,"Parkes");
obs2.toa = 53124.54;
obs2.freq = 1423.0;
strcpy(obs2.telID,"Parkes");
// Note that you cannot simply print out a structure (learn C++ for that!)
printf("The first toa is %lf\n",obs1.toa);
printf("The time between the two toas is %lf\n",obs2.toa-obs1.toa);
}
If a pointer is being used then the -> symbol is used instead of the "." symbol:
typedef struct psrObs { // Define a new type of variable called a psrObs. Note that this is before the main() function
double toa;
double freq;
char telID[16];
} psrObs;
void setObservations(psrObs *obs); // Define a function that will set the observations
int main()
{
psrObs obs;
setObservations(&obs);
printf("We have a TOA at %lf\n",obs.toa); // Notice the "." here instead of the "->"
}
void setObservations(psrObs *obs)
{
obs->toa = 51200.0;
obs->freq = 1400.0;
strcpy(obs->telID,"Parkes");
}