General-purpose programming on GPU
C and C++ programming basics
Giuseppe Bilotta, Eugenio Rustico, Alexis Hérault
DMI — Università di Catania
Sezione di Catania — INGV
C programming basics (part 1)
Hello world
Hello world example
#include <stdio.h>
/* Main function: say hello, return signaling no issues */
int main(void)
{
printf("Hello world!\n");
return 0; /* this is optional for main() */
}
| Hello world!
|
Plain Old Data (POD) types
Size (in bytes) of POD types
#include <stdio.h>
/* Main function: show type sizes */
int main(void)
{
/* can be signed or unsigned */
char achar;
short ashort;
int anint;
long along;
/* only signed */
float afloat;
double adouble;
long double alongdouble;
printf("Size of char: %u bytes\tvalue: %d\n", sizeof(achar), achar);
printf("Size of short: %u bytes\tvalue: %d\n", sizeof(ashort), ashort);
printf("Size of int: %u bytes\tvalue: %d\n", sizeof(anint), anint);
printf("Size of long: %u bytes\tvalue: %ld\n", sizeof(along), along);
printf("Size of float: %u bytes\tvalue: %f\n", sizeof(afloat), afloat);
printf("Size of double: %u bytes\tvalue: %lf\n", sizeof(adouble), adouble);
printf("Size of long double: %u bytes\tvalue: %llf\n", sizeof(alongdouble),
alongdouble);
}
| Size of char: 1 bytes value: -6
Size of short: 2 bytes value: 32729
Size of int: 4 bytes value: 11013
Size of long: 8 bytes value: 4195776
Size of float: 4 bytes value: 0.000000
Size of double: 8 bytes value: 0.000000
Size of long double: 16 bytes value: 0.000000
|
Initialization for POD types
#include <stdio.h>
/* Main function: show type sizes */
int main(void)
{
char achar = 'X' ; /* character constant */
short ashort = 0x0f; /* hexadeciaml constant */
int anint = 017; /* octal constant */
long along = -15L; /* decimal constant, long */
unsigned uiv = 42U; /* decimal constant, unsigned */
unsigned long ulv = 42UL; /* decimal constant, unsigned long */
/* floating-point constants */
float afloat = 12.3e-5f; /* float */
double adouble = 12.3e-14; /* double */
long double alongdouble = 12.3e-64L; /* long double */
printf("Size of char: %u bytes\tvalue: %d\n", sizeof(achar), achar);
printf("Size of short: %u bytes\tvalue: %d\n", sizeof(ashort), ashort);
printf("Size of int: %u bytes\tvalue: %d\n", sizeof(anint), anint);
printf("Size of unsigned int: %u bytes\tvalue: %d\n", sizeof(uiv), uiv);
printf("Size of long: %u bytes\tvalue: %ld\n", sizeof(along), along);
printf("Size of unsigned long: %u bytes\tvalue: %ld\n", sizeof(ulv), ulv);
printf("Size of float: %u bytes\tvalue: %g\n", sizeof(afloat), afloat);
printf("Size of double: %u bytes\tvalue: %lg\n", sizeof(adouble), adouble);
printf("Size of long double: %u bytes\tvalue: %llg\n", sizeof(alongdouble),
alongdouble);
}
| Size of char: 1 bytes value: 88
Size of short: 2 bytes value: 15
Size of int: 4 bytes value: 15
Size of unsigned int: 4 bytes value: 42
Size of long: 8 bytes value: -15
Size of unsigned long: 8 bytes value: 42
Size of float: 4 bytes value: 0.000123
Size of double: 8 bytes value: 1.23e-13
Size of long double: 16 bytes value: 1.23e-63
|
Enumerations
#include <stdio.h>
/* Named enumeration */
/* Enumerations start at 0 and the values grow by one */
enum weekday {
SUN,
MON,
TUE,
WED,
THU,
FRI,
SAT
};
int main(void)
{
enum weekday today = FRI;
/* Unnamed enumeration */
/* A particular value can be forced at any moment */
enum {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
} month = MAR;
printf("The month is: %u\n", month);
printf("Weekday for today: %u\n", today);
printf("Is it friday yet? ");
if (today == FRI)
printf("Yes!");
else
printf("No :(");
printf("\n");
}
| The month is: 3
Weekday for today: 5
Is it friday yet? Yes!
|
Enumerations are used to name logically-grouped integer constants.
Operators
C operator table
Operator | Meaning | Example | Result |
! | negation | !a | 1 if a == 0 , 0 otherwise |
- | change sign | a = 1 ; -a | -1 |
~ | bitwise complement | a = 0x01 ; ~a | 0xfe |
++ | preincrement | a = 1 ; ++a | 2 and a == 2 |
++ | postincrement | a = 1 ; a++ | 1 and a == 2 |
-- | predecrement | a = 1 ; --a | 0 and a == 0 |
-- | postdecrement | a = 1 ; a-- | 1 and a == 0 |
+ | addition | a = 1 ; b = 2; a + b | 3 |
- | subtraction | a = 3 ; b = 1; a - b | 2 |
* | multiplication | a = 3 ; b = 2; a * b | 6 |
& | bitwise AND | a = 3 ; b = 2; a & b | 2 |
| | bitwise OR | a = 3 ; b = 2; a | b | 3 |
^ | bitwise XOR | a = 3 ; b = 2; a ^ b | 1 |
<< | left shift | 1<<8 | 256 |
>> | right shift | 1024>>8 | 4 |
&& | logical AND (short-circuit) | |
|| | logical OR (short-circuit) | |
== | equality | |
? : | conditional operator | a ? b : c | b if a is true, else c |
Complex types
One-dimensional arrays (vectors)
#include <stdio.h>
int main(void)
{
unsigned int primes[6] = { 2, 3, 5, 7, 11 };
unsigned int composites[] = { 4, 6, 8, 9 };
int i;
unsigned int v[32];
unsigned int numcomposites = sizeof(composites)/sizeof(composites[0]);
for (i = 0; i < 32; i++)
v[i] = 1 << i;
printf("size of composites: %u, number of elements %u\n",
sizeof(composites), numcomposites);
printf("v[#composites]: %u\n",
v[numcomposites]);
}
| size of composites: 16, number of elements 4
v[#composites]: 16
|
Strings
#include <stdio.h>
int main(void)
{
/* A string is a char array, terminated by a NULL byte (value 0) */
char str[] = "life sentence";
int i;
/* Print each character of the string until a NULL byte (is encountered) */
for (i = 0; str[i] != '\0'; i++)
printf("%c", str[i]);
printf("\n");
/* built-in format: %s prints a string */
/* how many characters are there in a string? */
printf("%s [len: %u]\n", str, sizeof(str));
}
| life sentence
life sentence [len: 14]
|
Structures
/* structures are aggregate types. members can have the same or different types */
struct point {
int x;
int y;
};
struct irc_user {
char nickname[256];
enum { NONE, VOICE, OP } role;
};
int main(void)
{
struct irc_user ltworf = { "LtWorf", NONE };
struct point pt;
pt.x = 0;
pt.y = 1;
}
|
Unions
#include <stdio.h>
/* unions are _alternative_ data types: members start at the same
memory location, and are not independent of each other */
union key {
int ival;
float fval;
};
enum key_type {
INT,
FLOAT
};
struct entry {
enum key_type type;
union key key;
};
int main(void)
{
struct entry x;
x.type = INT;
x.key.ival = 3;
printf("ival=%d\n", x.key.ival);
x.type = FLOAT;
x.key.fval = 2.0;
printf("fval=%f\n", x.key.fval);
/* strange result! we read as an int the memory that was written as a float */
printf("ival=%d\n", x.key.ival);
}
| ival=3
fval=2.000000
ival=1073741824
|
Functions
A function has a return type, a name, zero or more arguments and a
body.
A function must be ‘known’ before it's used. It can be declared before
it's defined, or it can be defined directly.
int somefunc(int arg1, float arg2); /* declaration */
/* a function that is defined without a previous declaration */
void someotherfunc(void) {
/* do something */
/* we can use somefunc() because it was declared beforehand */
int someval = somefunc(1, 0.2f);
/* do something else */
}
int somefunc(int arg1, float arg2) {
/* definition */
return someint;
}
The return type and arguments of the function are its signature.
The same signature must be used when declaring and defining the
function.
Function example
#include <stdio.h>
union key {
int ival; float fval;
};
enum key_type { INT, FLOAT };
struct entry {
enum key_type type;
union key key;
};
void print_entry(struct entry e)
{
switch(e.type) {
case FLOAT:
printf("fval=%f\n", e.key.fval);
break;
case INT:
printf("ival=%f\n", e.key.ival);
break;
default:
printf("this can't happen\n");
}
}
int main(void)
{
struct entry x;
x.type = INT; x.key.ival = 3; print_entry(x);
x.type = FLOAT; x.key.fval = 2.0; print_entry(x);
}
| ival=0.000000
fval=2.000000
|
Pass by value
#include <stdio.h>
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main(void)
{
int x = 1;
int y = 0;
swap(x, y);
/* nothing happened! */
printf("x=%d,y=%d\n", x, y);
}
| x=1,y=0
|
Parameters are passed by value, the original variables are not
touched by the function.
Scope and visibility
Scope of a variable
#include <stdio.h>
/* visible in the whole file*/
int x = 2;
void foo(void)
{
int i;
/* only visible in this function,
hides the global variable */
int x = 1;
printf("\tfoo\t\t%d\n", x);
for (i = 0; i < 3; i++) {
/* only visible in the for block,
hides the x variable defined in the function */
int x = 3+i;
printf("\t\tfor\t%d\n", x);
}
printf("\tfoo\t\t%d\n", x);
}
int main(void)
{
printf("main\t\t\t%d\n", x);
foo();
printf("main\t\t\t%d\n", x);
}
| main 2
foo 1
for 3
for 4
for 5
foo 1
main 2
|
Global variables and functions, extern
visibility
#include <stdio.h>
/* cvar is visible by all modules linked to this file */
int cvar;
void bar(void)
{
extern int cvar;
printf("bar reads %d\n", cvar);
cvar = 1;
printf("bar writes %d\n", cvar);
}
void baz(void)
{
printf("baz reads %d\n", cvar);
cvar = 3;
printf("baz writes %d\n", cvar);
}
| #include <stdio.h>
extern int cvar;
extern void bar(void);
extern void baz(void);
void foo(void)
{
printf("foo reads %d\n", cvar);
cvar = 2;
printf("foo writes %d\n", cvar);
}
int main(void)
{
foo();
bar();
baz();
printf("main reads %d\n", cvar);
}
|
foo reads 0
foo writes 2
bar reads 2
bar writes 1
baz reads 1
baz writes 3
main reads 3
|
File-local variables and functions, static
visibility
#include <stdio.h>
/* cvar is only visible in this module */
static int cvar;
void bar(void)
{
extern int cvar;
printf("bar reads %d\n", cvar);
cvar = 1;
printf("bar writes %d\n", cvar);
}
/* baz is only visible in this module */
static void baz(void)
{
printf("baz reads %d\n", cvar);
cvar = 3;
printf("baz writes %d\n", cvar);
}
| #include <stdio.h>
extern int cvar; /* fails */
extern void bar(void); /* works */
extern void baz(void); /* fails */
void foo(void)
{
printf("foo reads %d\n", cvar);
cvar = 2;
printf("foo writes %d\n", cvar);
}
int main(void)
{
foo();
bar();
baz();
}
|
/tmp/ccpginnD.o: In function `foo':
static-main.c:(.text+0x6): undefined reference to `cvar'
static-main.c:(.text+0x1b): undefined reference to `cvar'
static-main.c:(.text+0x25): undefined reference to `cvar'
/tmp/ccpginnD.o: In function `main':
static-main.c:(.text+0x4d): undefined reference to `baz'
collect2: ld returned 1 exit status
|
File-local variables and functions, static
visibility (correct)
#include <stdio.h>
/* cvar is only visible in this module */
static int cvar;
/* baz is only visible in this module */
static void baz(void)
{
printf("baz reads %d\n", cvar);
cvar = 3;
printf("baz writes %d\n", cvar);
}
void bar(void)
{
extern int cvar;
printf("bar reads %d\n", cvar);
cvar = 1;
printf("bar writes %d\n", cvar);
baz();
}
| #include <stdio.h>
extern void bar(void); /* works */
int main(void)
{
bar();
}
|
bar reads 0
bar writes 1
baz reads 1
baz writes 3
|
Pointers
Simple pointer example
#include <stdio.h>
int main(void)
{
int a = 1;
/* a POINTER: its value is the address of a memory location */
/* we set it to the address of a */
int *ptr = &a;
printf("%d\n", a);
/* write the value 2 at the address pointed at by ptr */
*ptr = 2;
printf("%d\n", a);
/* preincrement the address pointed to by ptr */
++*ptr;
/* postincrement the address pointed to by ptr */
(*ptr)++;
printf("%d\n", a);
}
| 1
2
4
|
Pointers used to pass values by reference
#include <stdio.h>
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int x = 1;
int y = 0;
swap(&x, &y);
/* the values are now swapped */
printf("x=%d,y=%d\n", x, y);
}
| x=0,y=1
|
Pointer arithmetics
#include <stdio.h>
int main(void)
{
char cv[10];
short sv[10];
int iv[10];
char *cptr;
short *sptr;
int *iptr;
cptr = cv;
/* add 1 => increment by one byte (sizeof(char)) */
printf("%p,%p\n", cptr, cptr+1);
sptr = sv;
/* add 1 => increment by sizeof(short) */
printf("%p,%p\n", sptr, sptr+1);
iptr = iv;
/* add 1 => increment by sizeof(int) */
printf("%p,%p\n", iptr, iptr+1);
}
| 0x7fff93ccd7c0,0x7fff93ccd7c1
0x7fff93ccd7a0,0x7fff93ccd7a2
0x7fff93ccd770,0x7fff93ccd774
|
Arrays are pointers to their first element
int main(void)
{
int i;
unsigned int v[32];
unsigned int *ptr;
/* the array v is the address of its first element,
so v and &v[0] are the same thing, and have type
unsigned int *
*/
ptr = &v[0];
ptr = v;
/* iterate over an array by moving the pointer */
for (i = 0; i < 32; i++) {
*ptr = 1 << i;
ptr++;
}
ptr = &v[0];
/* iterate over an array using pointer arithmetic*/
for (i = 0; i < 32; i++) {
/* equivalent of ptr[i] */
*(ptr+i) = 1 << i;
}
}
|
Pointers and struct
#include <stdio.h>
struct point { int x; int y; };
void init_point(struct point *pt, int i) {
/* equivalent to (*pt).x */
pt->x = i;
pt->y = 1<<i;
}
void print_point(const struct point *pt) {
printf("(%d,%d)\n", pt->x, pt->y);
}
int main(void)
{
struct point v[3];
int i;
for (i = 0; i < 3; ++i) {
init_point(v + i, i);
print_point(v + i);
}
}
| (0,1)
(1,2)
(2,4)
|
Void and null pointers
#include <stdio.h>
int main(void)
{
int a;
int *iptr;
/* void pointers: pointers to data of unspecified type */
void *vptr;
iptr = &a;
/* any pointer can be converted to a void pointer */
vptr = iptr;
/* you can't dereference a void pointer: this
*vptr = 1;
fails at compile time */
/* a void pointer can be converted to any other pointer type */
iptr = vptr;
/* The null pointer constant is an integer constant with
value 0 and type integer of void *. As a pointer, it
reads as a pointer to nothing, and thus an invalid pointer */
iptr = 0; /* or: iptr = NULL */
/* dereferencing a null pointer gives a runtime error */
*iptr = 1;
}
| Segmentation fault
|
Preprocessor
Library functions are declared in header files. A line like
is a preprocessor instructions that includes the header files so
that during compilation those declarations are available.
To use the actual functions you might need to link to external
libraries:
Example using the math library
#include <stdio.h>
#include <math.h>
int main(void) {
printf("Pi is %lf and the cosine of 2*Pi/3 is %lf\n",
M_PI, cos(2*M_PI/3));
}
| % gcc math-lib-example.c -o math-lib-example -lm
% math-lib-example
|
Pi is 3.141593 and the cosine of 2*Pi/3 is -0.500000
|
The math.h
header file also defines the constant M_PI
# define M_PI 3.14159265358979323846 /* pi */
# define M_PI_2 1.57079632679489661923 /* pi/2 */
# define M_PI_4 0.78539816339744830962 /* pi/4 */
# define M_PIl 3.1415926535897932384626433832795029L /* pi */
# define M_PI_2l 1.5707963267948966192313216916397514L /* pi/2 */
# define M_PI_4l 0.7853981633974483096156608458198757L /* pi/4 */
Get your feet wet
Allocate and init static vector
write a program that allocates and initializes a vector of 64
integers. Initialization should use half the index when the index is
even and one plus three times the index when the index is odd. Print
the values of the array
Operate on static vector
add a function to the program to compute the three-point average of
each element in the previous array. The three-point average is the
sum of the value, the previous value (if present) and the next value
(if present), divided by the number of added elements. Print the new
array.
Pointers to pointers and other nice stuff
Two-dimensional array (matrix)
#include <stdio.h>
#define N 3
#define M 4
int main(void)
{
int i, j;
int A[N][M];
int *ptr;
/* A is a vector with N elements of type int[M] */
for (i = 0; i < N; i++)
for (j = 0; j < M; j++)
A[i][j] = i - j;
ptr = &A[0][0];
for (i = 0; i < N; i++)
for (j = 0; j < M; j++) {
printf("%d,%d", A[i][j], *(ptr+i*M+j));
if (j == M - 1)
printf("\n");
else
printf("\t");
}
printf("\n%u\t%u\t%u\n", sizeof(A), sizeof(A)/sizeof(A[0]),
sizeof(A)/sizeof(A[0][0]));
}
| 0,0 -1,-1 -2,-2 -3,-3
1,1 0,0 -1,-1 -2,-2
2,2 1,1 0,0 -1,-1
48 3 12
|
Array of strings
#include <stdio.h>
int main(void)
{
char *strv[3] = { "foo", "bar", "baz" };
int i;
for (i = 0; i < 3; i++)
printf("%s\n", strv[i]);
}
| foo
bar
baz
|
Command-line processing
Command-line parameters
#include <stdio.h>
/* argc: number of parameters. argv: array of command-line parameters */
int main(int argc, char **argv)
{
int i;
/* argv[0] is (almost) always present, and matches the name with which
the program was called */
printf("%d params\n", argc);
for (i = 0; i < argc; i++)
printf("%s\n", argv[i]);
}
| 1 params
./code/param
|
4 params
./code/param
one
two
three
|
String-to-number conversion
#include <stdio.h>
#include <stdlib.h> /* for atoi() */
/* argc: number of parameters. argv: array of command-line parameters */
int main(int argc, char **argv)
{
int param;
if (argc < 2) {
fprintf(stderr, "give at least one parameter\n");
/* since this was an error return non-zero */
return 1;
}
/* skip argv[0] */
--argc;
++argv;
while (argc) {
param = atoi(argv[0]);
--argc;
++argv;
printf("read %d\n", param);
}
}
| read 1
read 2
read 3
|
String-to-number conversion functions in the standard library:
#include <stdlib.h>
/* defines the following conversion routines */
double atof (const char *str);
int atoi (const char *str);
long int atol (const char *str);
/* these set endptr to the address of the first character after the
parsed number. Useful if you want to parse a sequence of numbers
in the same string.
*/
double strtod (const char *str, char **endptr);
long int strtol (const char *str, char **endptr, int base);
unsigned long int strtol (const char *str, char **endptr, int base);
Memory management
Memory allocation
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int *vector;
int numels;
if (argc != 2) {
fprintf(stderr, "please specify a number\n");
return 1;
}
/* we know there is exactly one parameter now */
numels = atoi(argv[1]);
printf("allocating %d integers\n", numels);
/* malloc returns a void* which C promotes to int*
automatically in this case. In C++ automatic
promotion doesn't happen, so casting is a good habit */
vector = (int *)malloc(numels * sizeof(int));
if (vector == NULL) {
fprintf(stderr, "allocation failed\n");
return 1;
}
/* malloc returns memory with random contents */
printf("the first integer is %d\n", vector[0]);
free(vector); /* always remember to free your memory! */
}
| allocating 2048 integers
the first integer is 0
|
Memory allocation and initialization
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int *vector;
int numels;
if (argc != 2) {
fprintf(stderr, "please specify a number\n");
return 1;
}
/* we know there is exactly one parameter now */
numels = atoi(argv[1]);
printf("allocating %d integers\n", numels);
vector = (int *)calloc(numels, sizeof(int));
if (vector == NULL) {
fprintf(stderr, "allocation failed\n");
return 1;
}
/* calloc returns memory initialized to 0*/
printf("the first integer is %d\n", vector[0]);
free(vector); /* always remember to free your memory! */
}
| allocating 2048 integers
the first integer is 0
|
Get your feet wet/2
Dynamic vectors
Fix the program from the previous exercise to work on
a vector of arbitrary size, with the number of elements specified as the
first parameter on the command line.