|
Ever since the second computer was invented, the problem of portability has been a thorn in the flesh for computer programmers. Machines have different instruction sets, different character sets, different word sizes, different memory architectures, different file systems, different graphics capabilities, different operating systems -- in short, different everythings! Consequently, it can be very difficult to write a program that will run, unmodified, on just about any machine. The situation is not completely without hope, though. In many cases, the problem domain is not tied to any one machine, and can be stated in machine-independent terms. The existence of high-level languages makes the task of implementing models for such problems relatively trouble-free, although some care must still be taken. On these pages, you will find quite a few hints and tips for making your code portable. Since the C language is just about the most portable language in existence, the focus of this part of the site is primarily on that language (although some of the ideas I present here may well be useful for other languages too). Sometimes, programmers use ideas, methods, tools, and techniques that are not 100% portable, but which have general applicability across two or more disparate operating systems and implementations. Where I have something to say about such cases, I've said it here. For example, you will find a discussion of library-building herein, even though each machine's (and, on occasion, each compiler's) library mechanism is different, because the library-building concept itself has widespread applicability. I am often asked why I consider portability to be so important. What (people ask) is the point of striving to make one's code portable, when one knows perfectly well that the code is going to be run only on one particular operating system, will only ever be run on one particular compiler, and in any case has to make use of distinctly non-portable calls to graphics libraries, networking libraries, and so on? In reply, I would raise several points. Firstly, any useful program will eventually be ported. That may be because its original environment (OS, compiler, whatever) has died, or it may be because the pressure from users of other environments to use the program becomes irresistible, or it may simply be that the organisation is moving its code lock, stock and barrel to a new OS. Whatever the reason, useful programs do get ported, and it therefore makes sense to write those programs in such a way as to ease the transition. Even if a program cannot be written 100% portably, it is generally possible to make most of it portable. I once worked for a company whose principal product is a Web browser; it had to run in a variety of environments, which included several set-top boxes. By careful writing, they had managed to make the code quite literally 99% portable. They could port the browser to a new environment by rewriting only around five thousand lines of code, out of a total of half a million or so. This paid off in real cash money; instead of taking a man-year or so to port the browser to a new platform, they could do so in just two to four man-weeks. (Yes, it would be more politically correct to say "person-weeks". If you think this matters, click the "Refresh" button on your browser until the text says what you wanted it to say. The rest of us will wait here for you. Honest!) But even if that were not the case, it would still make sense to write your code as portably as you can. With the possible exception of your garrulous host, most C programmers don't often talk about code re-use (often because it doesn't occur to them that it's anything special; doesn't everyone write their code to be re-usable?), but they do it rather a lot all the same. And the more portable your code, the more widely it can be re-used. Code can be gathered into libraries, and the libraries can be copied from one environment to another and re-built there. Even if you don't want to take your programs with you when you shift to a new OS (or a new version of an old OS), do you really want to rewrite all your favourite libraries? No? Well, if you write your libraries portably, you won't have to. But even if that were not the case, it would still make sense to write your code as portably as you can. Let us take the most obvious example - "learning" code. When you are learning to program, you generally write a lot of throwaway programs. They're not intended for re-use. They're merely textual representations of an attempt to learn a language concept.
Now, those of you who are guilty of committing IRC or Usenet
will no doubt be aware that those media are often used by
people seeking help with their program. Almost inevitably,
such people will be asked to send their code to a "paste
site", or include it in the body of their Usenet article.
And then the analysis will start. Just about the first thing
a would-be helper will do with your code (if they can't see
the problem straight away just by a quick glance) is to
compile it. And at that point, if the code isn't written
with portability in mind, your would-be helper might just shrug
and say "my compiler has never heard of
Okay, so if your program really needs console I/O functionality, then you have no choice. But most student programs typically do not need anything other than standard routines. Questions such as "how do I clear the screen in C?" are best answered: "Think carefully. Do you really need to clear the screen?" Sometimes, yes, you really do need to. But in the vast majority of cases (of student programs), there isn't really such a need; indeed, programs which arbitrarily clear the screen are rather annoying to use (unless they put the cleared data back on the screen afterwards, and how many student programs bother to do that?). If you write your program in C (or C++), rather than in "a weird variant of C (or C++) that my compiler happens to recognise", you are much more likely to get the help you seek, and in fact that help is more likely to be clueful (because, whilst it is only a rule of thumb and will undoubtedly hide a few exceptions) it is a rule of thumb that the kind of people who make a fuss about portability are generally also the people who know most about the language you are trying to learn (because they've gone to the trouble of finding out what, precisely, the language definition guarantees is portable from one compiler to another; consequently, and as a direct result of this exposure to and study of the language definition, they tend to know the actual language much better than those who have not made such a study). Having said all that, it is certainly true that taking advantage of an implementation's extensions (e.g. for graphics, networking, or whatever) can vastly increase the conceptual space available to programmers. Whilst a number of applications can indeed be written with 100% portability, these tend to be batch processes (e.g. text processing programs, such as compilers, lints, compiler-compilers, code generators, Markov chain generators, and the like). The principal advantage of writing portable code remains that of allowing us to re-use our libraries in different environments. As a consequence, the primary focus of this part of the site is on library-writing; i.e. the preparation of "code repositories" which can be used in whatever way seems best, in whichever environment you happen to be using at the time. Portability doesn't always come cheaply. If, for example, you can assume that the letters in your character set are grouped together (as in ASCII), there are a couple of tricks you can use to speed up some text processing. But if you can't assume this, then of course those tricks are out of bounds, so you won't be able to take advantage of that extra speed. On the other hand, platform dependence doesn't always come cheaply, either. The cost of porting a program is significantly increased if it wasn't written specifically with porting in mind! Fortunately, there is a simple answer to this apparent dilemma, and it's the same that is given to anyone seeking to optimise their code. Write it properly "first. It's easier to make a correct program fast, than to make a fast program correct. Then, if (and only if) you identify that the principal reason for your program's unacceptably poor performance is the retention of portability, you might want to look at a platform-specific solution. Chances are good that it'll never happen like that, except in situations where no good portable solution really exists (e.g. finding the size of a file). In general, the real performance gains are to be found in choosing better algorithms, rather than shaving the odd microsecond here or there by using platform-based assumptions. In short, portability is a Good Thing, and can actually save you significant quantities of time and effort -- but only if you let it. |