![]()
![]()
COMP2100
Lab 9: The Eiffel/C InterfaceSummary
This class is about learning to incorporate routines written in C into Eiffel systems.
Aims
Learn how to write an Eiffel `wrapper' for a C routine, allowing it to be called from inside Eiffel systems.
Gain some appreciation of the complexities and trade-offs involved in multi-language programming.
Preparation
Review your notes from Lecture 17, Lecture 20, Lecture 21, Lecture 22 and Lecture 24. Refresh your understanding of Design by Contract.
Exercise 1 - Getting Started
The idea is to re-implement some of the features of class ARRAY using routines written in C. You could imagine that the performance of the standard library class has been found to be unsatisfactory, and we're developing a drop-in substitute that uses C code to increase efficiency. (In the last exercise you'll get a chance to test whether this really works or not.)
We'll be working on a system with the following three components:
Class FIXED_ARRAY_OF_INTEGER, stored in the file fixed_array_of_integer.e.
The C code implementation of some of the functions needed for class FIXED_ARRAY_OF_INTEGER, in the file array.c.
Class ARRAY_TESTER, which is a simple test program for your new array class, in the file array_tester.e.
For simplicity, the arrays we're implementing today are not resizable, nor is this a generic class. These are fixed size arrays of integers only (hence the name).
Download the three files listed above and take a few minutes to read through them and see what they do. You'll see that class ARRAY_TESTER is complete, but with a few lines commented out; that the C language part of the array implementation is complete; but that the Eiffel class FIXED_ARRAY_OF_INTEGER is incomplete. In particular, there is no communication yet between Eiffel and C.
You can still compile and run the program, although it won't do anything very interesting. Do this now. Look at the notes for Lecture 24 for how to compile this system. The root class is ARRAY_TESTER.
Once you have compiled the system, write a Makefile to automate the process. (Don't worry about an Ace file.) Make sure that your Makefile takes the dependencies into account correctly, so that it does as little work as possible for each recompilation of the system. In other words, if the C file hasn't changed, it shouldn't have to run the C compiler on it again.
Exercise 2 - Creation
The implementation of the creation routine make is missing, although its contract is given. In array.c there is a function new_array that uses the standard library routine calloc to return a pointer to enough memory to store n integers, and sets each element to zero. There are two things for you to do:
Write an Eiffel wrapper for new_array so that you can call it from inside class FIXED_ARRAY_OF_INTEGER. You will need to use the external keyword and the POINTER type from Lecture 24.
Take a moment to think about what preconditions there should be on this routine, and code them into a require clause.
Notice that this class has a private attribute storage: POINTER. The purpose of this is so that an object can keep track of where its array elements are stored. Write an implementation for make that uses new_array to set storage to the location of the memory needed for the array.
Hint: Be careful to take into account the different ranges of index values: in C array indices always run from 0 to n-1, while in Eiffel the indices run from lower to upper. Once you are finished, you should be able to un-comment-out the assertions in the ensure clause of make.
Recompile and run the system to make sure that this works OK. It should now print out the contents of an array with the correct bounds, but with every element equal to zero.
Exercise 3 - Access
The implementation of the access feature item is also missing. (It just returns Eiffel's default integer value, zero.) Again there is a useful C function c_item in array.c that you can use. Write an Eiffel wrapper for this function, and then write an implementation of item that uses this. Remember to adjust your indices so that the index you pass to the C side is the right one in the range 0..count. Notice that c_item treats a pointer as an array; this is standard practice in C. Again, take the time to add appropriate require clauses.
Again, recompile and run. There won't be any difference in the output yet, since we haven't implemented put.
Exercise 4 - Modification
We just need to implement put in order to have a basic working array class. Again you will find a C function c_put in array.c, that does pretty much what you need. Write a wrapper for it and a body for put.
Add require and ensure clauses. The require clause for c_put is particularly important. Without it, the C routine could try to write to just about any location in memory.
This time when you compile and run, you should get more interesting results. Once again, remember to convert your Eiffel index to a C index in the right range.
Exercise 5 - Sorting with C
So far there hasn't been much chance of significant performance differences between the Eiffel and C implementations. But with a serious computational operation like sorting, there could be a big difference.
Look at the commented-out lines in the make routine of the root class. These lines call two built-in features of class FIXED_ARRAY_OF_INTEGER: sort_ascending and sort_descending. We will implement one of these in C and the other in Eiffel.
In array.c you will see two functions c_sort_descending and c_swap that together implement the Quicksort algorithm for sorting a part of an array. Write an Eiffel wrapper for c_sort_descending, adding appropriate require and ensure clauses.
Fill in the body of sort_descending with an appropriate call to c_sort_descending.
Now un-comment-out the lines
a.sort_descending io.put_string ("Descending:%N") print_array (a)from array_tester.e, recompile and run. Make sure that this operation works correctly before continuing.
Exercise 6 - Sorting with Eiffel
Now you have to write the other sorting routine sort_ascending, that you are to implement in Eiffel rather than C. Here are the steps.
Write an Eiffel procedure quick_sort_region in fixed_array_of_integer.e that takes two parameters lo and hi and sorts the part of the array between lo and hi into ascending order. The implementation of this can be an exact translation into Eiffel of the C code for c_sort_descending, except that the direction of the inequality in the if statement inside the loop will need to be reversed.
(Oddly enough the routine sometimes gets called (recursively) with lo > hi without it being an error, so be careful with preconditions.)
Write a procedure swap that swaps two elements of the array. Don't just write a wrapper for c_swap; instead implement this completely in Eiffel, using calls to item and put.
Finally fill in the body of sort_ascending so that it calls quick_sort_region in such a way that it sorts the entire array.
You should now be able to un-comment-out the last remaining lines of array_tester.e and compile and run the test program in its entirety. Check that it works correctly.
*Exercise 7 - Comparing the speed
Download the code for class CPU_TIMER (in file cpu_timer.e) and its C implementation part in the file timer.c. Incorporate these into your system so that you can compare the speeds of the two sorting routines.
The specification for the C library call clock(), used in timer.c appears to have changed, so the code I wrote is no longer correct. You'll have to read the manual page (either for clock or for time.h) to find out how to correct this. It's not hard.
Now go crazy and use everything you've learned this semester:
Modify the root class so that it reads the array bounds from the command line.
Write a test script that runs your program on different sized data sets.
Add a new Makefile target that automates the testing process and produces a testing report giving a table of speed comparisons between the two sorting routines.
You'll find that the speed comparison is shocking. This is mostly because Eiffel is checking assertions. Once you are confident that your program is correct, follow these steps to recompile with assertion-checking turned off and optimisation turned on:
Remove all the intermediate .c and .o files with the command
clean array_testerCompile with maximum optimisation by adding the options
-boost -no_split -O3to your compile command. For the -O3 option, that's a capital letter `o', not a zero. If you're going to do this more than once, you might want to add a new Makefile target for this. Remember to include the clean command: when you do this, you have to restart the compilation from scratch.
You should now find that the speed comparison is a little more reasonable.
**Exercise 8 - Resizable generic arrays
This question goes well beyond the scope of this unit. Trying to implement a solution could be time-consuming, and will certainly require good C programming skills.
The arrays we've been dealing with have been fixed size: once created, there is no possibility of changing the number of elements. Think about the issues involved in removing this restriction. This would mean implementing such operations as add_last and removing the require clause on put. What possible strategies are there? What effect might this have on efficiency? If you have time, try implementing one of your strategies.
The other restriction we have made was on the type of the array elements. Eiffel arrays can be arrays of anything. How might you implement this? The solution is probably to have arrays of pointers, with each pointer giving the location of the array element (object). Think about the issues involved here. Do you think you can come up with a solution, or are there details you need to know of how SmallEiffel implements Eiffel objects in C?
![]()
![]()
Copyright © 2004, Ian Barnes, The Australian National University
Feedback & Queries to
comp2100@iwaki.anu.edu.au
Version 2004.2, 17 May 2004, 11:56:27