Skip to the content of the web site.

Using C++-like associations of structures with functions

In C++, Java, C#, and other object-oriented languages, there is the concept of a class which associates

  • Data (member variables, attributes, etc.),
  • Operations (member functions, methods, behaviors, etc.).

The three objectives of object-oriented programming are:

  • encapsulation,
  • inheritance, and
  • polymorphism.

Unfortunately, these cannot easily be transferred back to C. For example: Encapsulation requires that member variables be private—all fields in a struct are public. It is not possible to extend the member variables of a struct, so inheritance is not possible. Finally, the compiler must determine which function to call at compile time. Simulating these in C would require, for example, extensive use of function pointers. A more complete reference is "Object Orientated Programming in ANSI-C" by Axel Schreiner. A very simple implementation of object-oriented programming in C is available in the Simple Object-Oriented C package available at the SourceForge website.

One aspect of object-oriented programming that can be more easily implemented in C is the association between member functions and the member variables. This is useful if you want to design, for example, a container data structure. We will look at the following topics:

  1. Defining the struct
  2. Allocating Memory
  3. Initialization
  4. Defining member functions
  5. The destructor
  6. De-allocating memory
  7. Example

Defining the struct

Using typedef, give the structure a name:

typedef struct {
	/* member variables */
	int field_name;
	int *field_array;
} Struct_name;

This would be equivalent to

class Class_name {
	public:
		/* member variables */
		int field_name;
};

You can now declare local variables that are either instances of this structure or arrays of this structure:

	Struct_name var;
	Struct_name array[N];

Accessing the fields in a struct is identical to accessing the public member variables:

	var.field_name = 42;
	var.field_array = NULL;

	int i;

	for ( i = 0; i < N; ++i ) {
		array[i].field_name = i + 1;
		array[i].field_array = NULL;
	}

Allocating memory

In C, you must request a block of memory of the appropriate size from the operating system through a call to malloc. Malloc takes as its argument a positive integer indicating the number of bytes required. To determine the number of bytes required by a data structure, use the sizeof operator. Like new, malloc returns the address of the first byte of the block of memory that has been allocated:

	Struct_name *ptr = (Struct_name *) malloc( sizeof( Struct_name ) );
	Struct_name *dynamic = (Struct_name *) malloc( N * sizeof( Struct_name ) );

Like with C++, these are now pointers to structures, so we must use -> to de-reference if the pointer is referencing:

	ptr->field_name = 42;     /* Use -> */
	ptr->field_array = NULL;

	int i;

	for ( i = 0; i < N; ++i ) {
		Struct_name dynamic[i].field_name = i;     /* Use . */
		Struct_name dynamic[i].field_array = NULL;
	}

Note that in the second case, dynamic[0] references the struct at the first location in the array.

Initialization

In C++, the new operator together with the constructor allocates and initializes the instance of the class. In C, the initialization must occur separately from the memory allocation. This is usually performed through an initialization routine which is passed the address of the object being initialized:

void init_sn( Struct_name *this, int size ) {
	this->field_name = size;
	this->field_array = (int *) malloc( this->field_name * sizeof( int ) );

	int i;

	for( i = 0; i < this->field_name; ++i ) {
		this->field_array[i] = 0;
	}
}

where sn is an appropriate abbreviation of Struct_name.

The argument in this example has been very carefully given the name this. When you call a member function on an instance of a class, this is an implicit argument that is passed to the member function—you do not have to pass it explicitly. In C, you must explicitly pass the address of the instance of the structure you are modifying:

	Struct_name var;
	Struct_name array[N];

	init_sn( &var );

	int i;

	for ( i = 0; i < N; ++i ) {
		init_sn( &(array[i]) );
		/* You can also use init_sn( array + i ); */
	}

or

	Struct_name *ptr = (Struct_name *) malloc( sizeof( Struct_name ) );
	Struct_name *dynamic = (Struct_name *) malloc( N * sizeof( Struct_name ) );

	init_sn( &ptr );

	int i;

	for ( i = 0; i < N; ++i ) {
		init_sn( &(dynamic[i]) );
		/* You can also use init_sn( dynamic + i ); */
	}

Defining member functions

Member functions are simply those functions that take the address of the object that they are being operated on as the first argument.

return_type function_name_sn( Struct_name *this, parameter_list ) {
	/* function body */
	/* 'this' is a pointer to the instance the member function is called on */
}

This would be equivalent to the C++ member function

return_type Class_name::function_name( parameter_list ) {
	/* function body */
	/* 'this' is a pointer to the instance the member function is called on */
}

If you wanted to call another function, you would simply pass this on as the first argument.

An example using the above case is

int average_sn( Struct_name *this ) {
	int average, i;

	for ( i = 0; i < this->field_name; ++i ) {
		average += this->field_array[i];
	}

	return (average + this->field_name/2)/this->field_name;
}

The destructor

If your data structure requires clean-up before the memory is deallocated (the data structure has allocated memory, or some other resource such as open file handles), you require a second function. Such functions tend to be called destroy. Given our example above, such a function would be

void destroy_sn( Struct_name *this ) {
	free( this->field_array );
}

This must be called on all instances of the structure, including each individual entry in an array of the structure:

	Struct_name var;
	Struct_name array[N];

	init_sn( &var );

	int i;

	for ( i = 0; i < N; ++i ) {
		init_sn( &(array[i]) );
		/* You can also use init_sn( array + i ); */
	}

	/* ... in the same function and scope ... */

	destroy_sn( &var );

	int i;

	for ( i = 0; i < N; ++i ) {
		destroy_sn( &(array[i]) );
		/* You can also use destroy_sn( array + i ); */
	}

If the instances of the structure have been allocated dynamically, when we are ready to deallocate the memory for the instances (they are no longer useful), we must call

	Struct_name *ptr = (Struct_name *) malloc( sizeof( Struct_name ) );
	Struct_name *dynamic = (Struct_name *) malloc( N * sizeof( Struct_name ) );

	init_sn( &ptr );

	int i;

	for ( i = 0; i < N; ++i ) {
		init_sn( &(dynamic[i]) );
		/* You can also use init_sn( dynamic + i ); */
	}

	/* ... possibly in a completely different function ... */

	destroy_sn( ptr );

	for ( i = 0; i < N; ++i ) {
		destroy_sn( &(dynamic[i]) );
		/* You can also use destroy_sn( dynamic + i ); */
	}

De-allocating memory

Memory in C must be freed. If the structures were declared as local variable (as with var and array above), it is not necessary to free the memory: it was allocated on the stack and the compiler will ensure that that memory is deallocated. If the memory was allocated dynamically, that memory must also be deallocated. Thus, in the last example, we should really be calling:

	destroy_sn( ptr );
	free( ptr );

	for ( i = 0; i < N; ++i ) {
		destroy_sn( &(dynamic[i]) );
		/* You can also use destroy_sn( dynamic + i ); */
	}

	free( dynamic );

Example

An example of a singly linked list is provided in the source directory. There are two files:

  • Single_list.h
  • Single_list.c

There are two examples using this class:

  • local.c where Single_list is used to declare a local variable.
  • dynamic.c where the memory is allocated dynamically using malloc.

You can download these files and compile them. In Unix, you can use the commands:

$ gcc -c -Wall Single_list.c
$ gcc -Wall local.c Single_list.o
$ ./a.out
$ gcc -Wall dynamic.c Single_list.o
$ ./a.out

The first line generates a file Single_list.o and the output of both executables is

 -> 3 -> 4 -> 5 -> 6 -> 7
 -> 1 -> 2 -> 5 -> 6 -> 7
 -> 5 -> 6 -> 7

The flag -Wall indicates that all warnings should be displayed.