Friday, June 28, 2019

Addressing the C Standard's technical debt

When the C Standard was written, the C language had already been in wide use for over a decade.  Many implementations supported semantic features or guarantees that would not be practical on all platforms, and many programs for such implementations exploited those features to do things that otherwise couldn't be done as efficiently--if at all--on some other platforms.

If the Standard had sought to require that all implementations support the features necessary to run all of these programs, that would have made it impractical to implement on many problems.  If the Standard required that programs be limited to features that could be supported on all implementations, that would have made the language suitable only for tasks that all implementations could perform efficiently.  Rather than do either of these things, the authors of the Standard opted to compromise by allowing conforming programs that didn't need to be 100% portable to exploit "popular extensions" that were be supported on all platforms of interest, even if those extensions might be unsupportable on other platforms.  Meanwhile, the Standard only required that platforms support those features and guarantees which could either be supported on all platforms without added cost, or would be useful for a sufficiently wide range of tasks to justify the cost even on the platforms where they would be the most expensive.

Unfortunately, the authors of the Standard failed to make clear that they regarded their jurisdiction as being limited to constructs that all implementations would be *required* to support whether practical or not.  Constructs which implementations weren't required to support, but should generally support when practical, were outside their jurisdiction.  Consequently, they regarded both "Implementations should generally be expected to process X in fashion Y, but need not do if impractical" and "Implementations should not generally be expected to process X in any particular fashion" as equivalent to "The behavior of X is undefined".  The Standard makes no effort whatsoever to separate the concepts even in sub-cases where action Y would be practical on nearly all implementations.

Although there was no formal means of determining which implementations should be expected to support which features and guarantees, this wan't much of a problem in 1989.  Features and guarantees tended to be most useful on the platforms where they were cheapest to support (on many many platforms, upholding them 100% of the time would have been simpler than doing literally anything else).  Difficulties would naturally arise if programs that exploited such features from platforms where they were cheap needed to be ported to platforms where they weren't, but cost/benefit evaluations necessary to decide whether to have implementations support such features despite the cost, or whether to have programmers work around their absence, could more effectively be handled by the people familiar with the platform and the tasks to be performed than by the Committee.

Unfortunately, while questions of what was "practical" or "impractical" were often relatively clear-cut in 1990, they have become far less so today.  In 1990, if a program was only going to be

No comments:

Post a Comment