Here is the question, How did C (K&R C) look like? The question is about the first ten or twenty years of C's life?
I know, well I heard them from a prof in my uni, that C didn't have the standard libraries that we get with ANSI C today. They used to write IO routines in wrapped assembly! The second thing is that K&R book, is one the best books ever for a programmer to read, This is what my prof told us :)
I would like to know more about good ol' C. For example, what major difference you know about it compared to ANSI C, or how did C change programmers mind about programming?
Just for record, I am asking this question after reading mainly these two papers:
Evolving a language in and for the real world: C++ 1991-2006
A History of C++: 1979-1991
They are about C++, I know! thats why I wanna know more about C, because these two papers are about how C++ was born out of C. I am now asking about how it looked before that. Thanks Lazarus for pointing out to 1st edition of K&R, but I am still keen to know more about C from SO gurus ;)
Well, for a start, there was none of that function prototype rubbish. main() was declared thus:
/* int */ main(c,v)
int c;
char *v[];
{
/* Do something here. */
}
And there was none of that fancy double-slash comments either. Nor enumerations. Real men used #define.
Aah, brings a tear to my eyes, remembering the good old days :-)
Have a look at the 'home page' for the K&R book at Bell Labs, in particular the heading "The history of the language is traced in ``The Development of the C Language'', from HOPL II, 1993"
Speaking from personal experience, my first two C compilers/dev environments were DeSmet C (16-bit MS-DOS command line) and Lattice C (also 16-bit MS-DOS command line). DeSmet C came with its own text editor (see.exe) and libraries -- non-standard functions like scr_rowcol() positioned the cursor. Even then, however, there were certain functions that were standard, such as printf(), fopen() fread(), fwrite() and fclose().
One of the interesting peculiarities of the time was that you had a choice between four basic memory models -- S, P, D and L. Other variations came and went over the years, but these were the most significant. S was the "small" model, 16-bit addressing for both code and data, limiting you to 64K for each. L used 24-bit addressing, which was a 16-bit segment register and a 16-bit offset register to compute addresses, limiting you to 1024K of address space. Of course, in a 16-bit DOS world, you were confined to a physical limitation of 640K. P and D were compromises between the two modes, where P allowed for 24-bit (640K) code and 64K data, and D allowed for 64K code and 640K data addressing.
Wikipedia has some information on this topic.
Here is one example of the code that changed with ANSI C for the better:
double GetSomeInfo(x)
int x;
{
return (double)x / 2.0;
}
int PerformFabulousTrick(x, y, z)
int x, int y;
double z;
{
/* here we go */
z = GetSomeInfo(x, y); /* argument matching? what's that? */
return (int)z;
}
I first started working with C on VAX/VMS in 1986. Here are the differences I remember:
No prototypes -- function definitions and delcarations were written as
int main() /* no void to specify empty parameter list */
{
void foo(); /* no parameter list in declaration */
...
}
...
void foo(x,y)
int x;
double y;
{
...
}
No generic (void) pointer type; all of the *alloc() functions returned char * instead (which is part of why some people still cast the return value of malloc(); with pre-ANSI compilers, you had to);
Variadic functions were handled differently; there was no requirement for any fixed arguments, and the header file was named differently (varargs.h instead of stdarg.h);
A lot of stuff has been added to math.h over the years, especially in the C99 standard; '80s-vintage C was not the greatest tool for numerical work;
The libraries weren't standardized; almost all implementations had a version of stdio, math, string, ctype, etc., but the contents were not necessarily the same across implementations.
Look at the code for the Version 6 Unix kernel - that was what C looked like!
See Lion's Commentary on Unix 6th Edition (Amazon).
Also, it would be easier if you told us your age - your profile says you're 22, so you're asking about code prior to 1987.
Also consider: The Unix Programming Environment from 1984.
While for obvious reasons the core language came before the library, if you get hold of a first edition copy of K & R published in 1978 you will find the library very familiar. Also C was originally used for Unix development, and the library hooked into the I/O services of the OS. So I think your prof's assertion is probably apocryphal.
The most obvious difference is the way functions were defined:
VOID* copy( dest, src, len )
VOID* dest ;
VOID* src ;
int len ;
{
...
}
instead of:
void* copy( void* dest, void* src, int len )
{
...
}
for example. Note the use of VOID; K&R C did not have a void type, and typically VOID was a macro defined as int*. Needless to say, to allow this to work, the type checking in early compilers was permissive. From a practical point of view, the ability of C to validate code was poor (largely through lack of function prototypes and weak type checking), and hence the popularity of tools such a lint.
In 1978 the definition of the language was the K&R book. In 1989 it was standardised by ANSI and later by ISO, the 2nd edition is no longer regarded as the language definition, and was based on ANSI C. It is still the best book on C IMO, and a good programming book in general.
There is a brief description on Wikipedia which may help. Your best bet is to get a first edition copy of K&R, however, I would not use it to learn C, get a 2nd ed. for that.
I started using C in the early 1980's. The key difference I've seen between now and then was that early C did not have function prototypes, as someone noted. The earliest C I ever used had pretty much the same standard library as today. If there was a time when C didn't have printf or fwrite, that was before even my time! I learned C from the original K&R book. It is indeed a classic, and proof that technically sophisticated people can also be excellent writers. I'm sure you can find it on Amazon.
You might glance at the obfuscated C contest entries from the time period you are looking for.
16 bit integers were quite common in the ol' days.
Related
I was playing around with an online PDP11 emulator (link) and was looking at the programming section of its FAQ.
It says this about programming in C on the emulator:
You need to write pre-K&R C which is quite a bit different from modern C
I believe this is referring to the version of C in use before the publication of The C Programming Language. I have tried to understand this version by reading the sparse C files I can find in the emulator's filesystem, but even simple things like declaring argc and argv have eluded me. I also can't find anything about it online.
Is there any documentation, written at the time or after the fact, on "pre-K&R" C?
For this sort of question, my go-to source is the archived web page of the late Dennis Ritchie:
https://www.bell-labs.com/usr/dmr/www/
From there it's one click to this early reference manual for C, written by Ritchie himself:
https://www.bell-labs.com/usr/dmr/www/cman.pdf
This is indeed "pre-K&R C", featuring anachronisms such as =+ instead of +=.
This is the same reference manual that appeared, in updated form, as Appendix A in the K&R book.
On the same page are links to several other versions of that reference manual, as well as notes about and even the source code of two early versions of Ritchie's compiler. These are pretty fun to look at, although as the page notes, "You won't be able to compile them with today's compilers".
There's a whole Stack Exchange site dedicated to questions like these: https://retrocomputing.stackexchange.com/.
Steve Summit answered about where to get the documentation. So this post is intended to summarize noticeable differences from modern C
Types for function arguments were quite different. They were not specified within the parenthesis
void foo(a, b)
int a;
float b;
{
// Function body
}
No row comments, like //. Only /* */
No logical or and and. The operators && and || did not exist. The bitwise operators & and | were used instead.
=- meant the same as -=, which lead to ambiguity with for instance x=-1.
There was no void pointers. char pointers were used instead.
One could not declare variables in the for header, so one had to write:
int i=0;
for(i=0; i<N; ++i)
= were only used for assignments and not initialization. Those were done like this: int x 3;
Implicit int. This is still valid in modern C. The difference is that no (sane) programmer is using it anymore. This:
foo 3;
is actually equivalent to
int foo = 3;
There was no const qualifier
I am new to coding using MISRA C guidelines.
The following are two rules in MISRA C 2004:
Rule 16.1 (required): Functions shall not be defined with a variable number of arguments.
Rule 20.9 (required): The input/output library <stdio.h> shall not be used in production code.
This clearly means that I can't use printf in production code for it to be MISRA C compliant, because printf is a part of <stdio.h> and allows a variable number of arguments. So I set out on a quest to find out how I can write my own printf statement. So far I am unable to find any solution for this predicament. Any help from fellow developers would be appreciated.
so far I am unable to find any solution for this predicament
You have to use functions that print one (countable) things at a time. An example interface you might want to implement might look like the following:
print_string("Hello");
print_int(5);
print_char('\n');
so I set out on a quest to find out how I can write my own printf statement
Most MISRA-C systems are embedded systems where printf is just some bloated wrapper around an UART library. The usual solution is to develop your own logging/messaging tool instead. Not necessarily UART-based, might as well some other serial bus, or just 8 parallel data or some LCD/7-seg... all depending on what you need to display and if you intend for this to be part of the production code or not.
So how to do this is highly project-specific and it's typically more of a system design and electronics problem than a programming one.
EDIT
Since you seem to be making some sort of general-purpose library, one solution is to simply provide an API that returns strings to the caller, then let the caller worry about how to present them. That makes your lib MISRA-C compliant, while allowing the caller to print strings in whatever application-specific way they have available. For example:
void lib_getmsg (char* msg, size_t bufsize);
Where "lib" is some prefix for your library. Leave string allocation to the caller. Alternatively, the old-fashioned way:
lib_result_t lib_dosomething (void);
// Returns LIB_OK if went OK, returns LIB_ERR in case of errors.
// To get more information, call lib_get_lastmsg.
const char* lib_get_lastmsg (void);
This returns a pointer to an internal static string allocated by your library. The downside of this is that it won't work well in multi-process environments.
You need to understand the rationale for the MISRA C guidelines, understand the context they are used in, and the circumstances of your own code.
You also need to understand that the MISRA Guidelines are not to be blindly followed with a tick-box mentality... you then need to appreciate that those nice folk at MISRA provide several chapters of useful material before the actual guidelines. Part of that is the Deviation procedure.
If you can justify why you feel you need to violate a guidelines, then use the deviation procedure that is specified. This requires you understand the nature of the violation, and what you are going to do to ensure the integrity of your application.
If you genuinely need to use printf() and you can justify that, use it with a deviation
On Linux, running on a modern x86_64 processor:
int main()
{
char *s = "Hello, World!\n";
long l = 14;
long fd = 1;
long syscall = 1;
long ret = 0;
__asm__("syscall"
: "=a"(ret)
: "a"(syscall),
"D"(fd),
"S"(s),
"d"(l));
return 0;
}
Output:
Hello, World!
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
C is an old language. For instance, did you know that there would be no problem to use the . operator instead of the -> operator with pointers and that the -> was maintained for legacy reasons? see here.
Studying several C code, one can notice that most of the time structs are passed around using pointers instead of by value. Arrays are also passed by pointer.
Would be possible to change the syntax to pass structures by default as pointers? For instance, consider the following example:
struct Point {
float x;
float y;
};
struct Line {
Point! p0; // ! means that we want a value, not a pointer
Point! p1;
};
struct Line2 {
Point p0; // would be the same as Point*
Point p1;
}
void foobar(Line line) { // would mean Line* line
...
}
void foobaz(Line! line) { // would mean Line line
...
}
Let's not consider readability, for instance, some argue that -> makes explicit that is a pointer. That's not the point here.
Here is the question: what possible problems or corner cases could arise from using such syntax? For example: 'using such syntax, it would be impossible to do this or that'
Java actually works similar to this proposal: everything is a pointer under the hood. The difference is that Java has garbage collector and objects are always allocated in the heap (the object itself, not the 'pointer') while in C structs can be allocated in the stack (although most of the time I see allocated in the heap).
If one wonders why I am asking this, it is because I am creating a programming language for low level development and I want to optimize the syntax for the common case. Although I like the C language syntax itself, I've got frustated by having to use headers (update function signature in two places, for instance), lack of support for function overload (I don't like to write int_list_add, float_list_add) and similar.
Thank you all who already replied, but I am not trying to modify C. This is a research for my language that I don't have pretension to be used by others.
My language is for low-level development trying as much as possible to use high level stuff like OO stuff, high level functions, closures and so on. It won't be garbaged collector, using a memory model similar to C (similar, not equal).
The language will have pointers just as C: you can get the address of some variable, deference a pointer.
If it is so important to ask the question that I asked, here go the syntax of my language which, by the way, it is totally different from C, a proof that I am not trying to create a C clone.
record Point:
x : float
y : float
record Line:
p0 : Point!
p1 : Point!
record Line2:
p0 : Point
p1 : Point
function foobar : void
#line : Line
function foobaz : void
#line : Line!
As I've said, I want to optime the syntax of my language for the common case. So if I am going to pass by pointer almost all the time, I don't want to be using a * again, and again and again. The only problem (and this has nothing to do with my programming skills, but is philosofical issue) is that I can't see problems that could arise from not specifying explicitly that is a pointer.
You can change the syntax and the semantics (actually; much more important than just syntax) of C, but you should not call that language C anymore.
C is a standard language; read first the wikipage on C11 and the n1570 draft (or buy, from ISO or your national standard body, the paper equivalent)
I am not a lawyer, and I don't know if and how ISO is legally protecting the standard. (However, be sure that if you call your stuff C, people would surely laugth at you).
For example: 'using such syntax, it would be impossible to do this or that'
Nothing would be impossible (both your language and the C language are Turing-complete). Some things might become more inconvenient.
BTW, you don't describe precisely enough what you are thinking about (examples are not describing programming languages, and you don't explain semantics aspects). So we can't comment on it.
Read several programming languages specifications, including not only n1570 but also n3337 (close to C++11 specification) and of course R5RS (a short, well written, specification of some Scheme dialect)
At last, be sure to implement your programming language (and name it differently than C). I recommend to implement it as some free software (e.g. on github) and to bootstrap your compiler.
So try to specify your language as well as C, or C++, or Scheme are specified. Then implement. You could need decades of work, and you certainly need several years.
Read at first the Dragon Book, SICP & Lisp In Small Pieces. Read also about Denotational Semantics
Consider implementing your language with the help of libraries like GCCJIT or LLVM, or by compiling it to C. Then you'll have a "proof-of-concept" implementation. Have fun doing it.
By implementing your language, you'll discover its semantics traps. (I love doing that).
Look also into other languages, like Ada, Go, Ocaml, Rust, Modula, Cyclone, Scheme, C++ (perhaps you are reinventing its references, or its smart pointers), Common Lisp ....
Read Scott's Programming Languages Pragmatics book.
So I'm completely new to programming. I currently study computer science and have just read the first 200 pages of my programming book, but there's one thing I cannot seem to see the difference between and which havn't been clearly specified in the book and that's reserved words vs. standard identifiers - how can I see from code if it's one or the other.
I know the reserved words are some that cannot be changed, while the standard indentifiers can (though not recommended according to my book). The problem is while my book says reserved words are always in pure lowercase like,
(int, void, double, return)
it kinda seems to be the very same for standard indentifier like,
(printf, scanf)
so how do I know when it is what, or do I have to learn all the reserved words from the ANSI C, which is the current language we are trying to learn, (or whatever future language I might work with) to know when it is when?
First off, you'll have to learn the rules for each language you learn as it is one of the areas that varies between languages. There's no universal rule about what's what.
Second, in C, you need to know the list of keywords; that seems to be what you're referring to as 'reserved words'. Those are important; they're immutable; they can't be abused because the compiler won't let you. You can't use int as a variable name; it is always a type.
Third, the C preprocessor can be abused to hijack anything; if you compile with #define double int in effect, you get what you deserve, but there's nothing much to stop you doing that.
Fourth, the only predefined variable name is __func__, the name of the current function.
Fifth, names such as printf() are defined by the standard library, but the standard library has to be implemented by someone using a C compiler; ask the maintainers of the GNU C library. For a discussion of many of the ideas behind the treaty between the standard and the compiler writers, and between the compiler writers and the programmers using a compiler, see the excellent book The Standard C Library by P J Plauger from 1992. Yes, it is old and the modern standard C library is somewhat bigger than the one from C90, but the background information is still valid and very helpful.
Reserved words are part of the language's syntax. C without int is not C, but something else. They are built into the language and are not and cannot be defined anywhere in terms of this particular language.
For example, if is a reserved keyword. You can't redefine it and even if you could, how would you do this in terms of the C language? You could do that in assembly, though.
The standard library functions you're talking about are ordinary functions that have been included into the standard library, nothing more. They are defined in terms of the language's syntax. Also, you can redefine these functions, although it's not advised to do so as this may lead to all sorts of bugs and unexpected behavior. Yet it's perfectly valid to write:
int puts(const char *msg) {
printf("This has been monkey-patched!\n");
return -1;
}
You'd get a warning that'd complain about the redefinition of a standard library function, but this code is valid anyway.
Now, imagine reimplementing return:
unknown_type return(unknown_type stuff) {
// what to do here???
}
The Wikipedia article on ANSI C says:
One of the aims of the ANSI C standardization process was to produce a superset of K&R C (the first published standard), incorporating many of the unofficial features subsequently introduced. However, the standards committee also included several new features, such as function prototypes (borrowed from the C++ programming language), and a more capable preprocessor. The syntax for parameter declarations was also changed to reflect the C++ style.
That makes me think that there are differences. However, I didn't see a comparison between K&R C and ANSI C. Is there such a document? If not, what are the major differences?
EDIT: I believe the K&R book says "ANSI C" on the cover. At least I believe the version that I have at home does. So perhaps there isn't a difference anymore?
There may be some confusion here about what "K&R C" is. The term refers to the language as documented in the first edition of "The C Programming Language." Roughly speaking: the input language of the Bell Labs C compiler circa 1978.
Kernighan and Ritchie were involved in the ANSI standardization process. The "ANSI C" dialect superceded "K&R C" and subsequent editions of "The C Programming Language" adopt the ANSI conventions. "K&R C" is a "dead language," except to the extent that some compilers still accept legacy code.
Function prototypes were the most obvious change between K&R C and C89, but there were plenty of others. A lot of important work went into standardizing the C library, too. Even though the standard C library was a codification of existing practice, it codified multiple existing practices, which made it more difficult. P.J. Plauger's book, The Standard C Library, is a great reference, and also tells some of the behind-the-scenes details of why the library ended up the way it did.
The ANSI/ISO standard C is very similar to K&R C in most ways. It was intended that most existing C code should build on ANSI compilers without many changes. Crucially, though, in the pre-standard era, the semantics of the language were open to interpretation by each compiler vendor. ANSI C brought in a common description of language semantics which put all the compilers on an equal footing. It's easy to take this for granted now, some 20 years later, but this was a significant achievement.
For the most part, if you don't have a pre-standard C codebase to maintain, you should be glad you don't have to worry about it. If you do--or worse yet, if you're trying to bring an old program up to more modern standards--then you have my sympathies.
There are some minor differences, but I think later editions of K&R are for ANSI C, so there's no real difference anymore.
"C Classic" for lack of a better terms had a slightly different way of defining functions, i.e.
int f( p, q, r )
int p, float q, double r;
{
// Code goes here
}
I believe the other difference was function prototypes. Prototypes didn't have to - in fact they couldn't - take a list of arguments or types. In ANSI C they do.
function prototype.
constant & volatile qualifiers.
wide character support and internationalization.
permit function pointer to be used without dereferencing.
Another difference is that function return types and parameter types did not need to be defined. They would be assumed to be ints.
f(x)
{
return x + 1;
}
and
int f(x)
int x;
{
return x + 1;
}
are identical.
The major differences between ANSI C and K&R C are as follows:
function prototyping
support of the const and volatile data type qualifiers
support wide characters and internationalization
permit function pointers to be used without dereferencing
ANSI C adopts c++ function prototype technique where function definition and declaration include function names,arguments' data types, and return value data types. Function prototype enable ANSI C compiler to check for function calls in user programs that pass invalid numbers of arguments or incompatible arguments data types. These fix major weakness of the K&R C compiler.
Example: to declares a function foo and requires that foo take two arguments
unsigned long foo (char* fmt, double data)
{
/*body of foo */
}
FUNCTION PROTOTYPING:ANSI C adopts c++ function prototype technique where function definaton and declaration include function names,arguments t,data types and return value data types.function prototype enable ANSI ccompilers to check for function call in user program that passes invalid number number of argument or incompatiblle argument data types.these fix a major weakness of the K&R C compilers:invalid call in user program often passes compilation but cause program to crash when they are executed
The difference is:
Prototype
wide character support and internationalisation
Support for const and volatile keywords
permit function pointers to be used as dereferencing
A major difference nobody has yet mentioned is that before ANSI, C was defined largely by precedent rather than specification; in cases where certain operations would have predictable consequences on some platforms but not others (e.g. using relational operators on two unrelated pointers), precedent strongly favored making platform guarantees available to the programmer. For example:
On platforms which define a natural ranking among all pointers to all objects, application of the relational operators to arbitrary pointers could be relied upon to yield that ranking.
On platforms where the natural means of testing whether one pointer is "greater than" another never has any side-effect other than yielding a true or false value, application of the relational operators to arbitrary pointers could likewise be relied upon never to have any side-effects other than yielding a true or false value.
On platforms where two or more integer types shared the same size and representation, a pointer to any such integer type could be relied upon to read or write information of any other type with the same representation.
On two's-complement platforms where integer overflows naturally wrap silently, an operation involving an unsigned values smaller than "int" could be relied upon to behave as though the value was unsigned in cases where the result would be between INT_MAX+1u and UINT_MAX and it was not promoted to a larger type, nor used as the left operand of >>, nor either operand of /, %, or any comparison operator. Incidentally, the rationale for the Standard gives this as one of the reasons small unsigned types promote to signed.
Prior to C89, it was unclear to what lengths compilers for platforms where the above assumptions wouldn't naturally hold might be expected to go to uphold those assumptions anyway, but there was little doubt that compilers for platforms which could easily and cheaply uphold such assumptions should do so. The authors of the C89 Standard didn't bother to expressly say that because:
Compilers whose writers weren't being deliberately obtuse would continue doing such things when practical without having to be told (the rationale given for promoting small unsigned values to signed strongly reinforces this view).
The Standard only required implementations to be capable of running one possibly-contrived program without a stack overflow, and recognized that while an obtuse implementation could treat any other program as invoking Undefined Behavior but didn't think it was worth worrying about obtuse compiler writers writing implementations that were "conforming" but useless.
Although "C89" was interpreted contemporaneously as meaning "the language defined by C89, plus whatever additional features and guarantees the platform provides", the authors of gcc have been pushing an interpretation which excludes any features and guarantees beyond those mandated by C89.
The biggest single difference, I think, is function prototyping and the syntax for describing the types of function arguments.
Despite all the claims to the contary K&R was and is quite capable of providing any sort of stuff from low down close to the hardware on up.
The problem now is to find a compiler (preferably free) that can give a clean compile on a couple of millions of lines of K&R C without out having to mess with it.And running on something like a AMD multi core processor.
As far as I can see, having looked at the source of the GCC 4.x.x series there is no simple hack to reactivate the -traditional and -cpp-traditional lag functionality to their previous working state without without more effor than I am prepered to put in. And simpler to build a K&R pre-ansi compiler from scratch.