Live data from Hacker News

Some dark corners of C

docs.google.com

161–170 of 175 posts

Re: Some dark corners of C

#161
post #115
post #83

Earlier quoted context omitted.

That's true (except for C), because I can't see how a create_function-defined function closes over its environment), but what I had in mind was the way in which the author of the documentation, obviously one of the PHP core developers, talks casually about "returning the name of an anonymous function". It shows just how much twisted the logic of these people is. But I suppose that goes naturally hand in hand with the…

> That's true (except for C), because I can't see how a create_function-defined function closes over its environment), That's why it would be a fake closure. $foo = someNumber(); create_function('', 'return '.$foo.';'); You would generate a new string to be evaluated as the function body each time. > talks casually about "returning the name of an anonymous function". It shows just how much twisted the logic of these…

"The point of an anonymous function isn't to make it not have a name, but to be able to define it where it is needed instead of referring to some specific function name in your code."

Actually, its point is to make it a value that can be referenced from any number of bindings (associations between names and values) in any number of scopes. What you're saying is just a consequence of this.

"It also fits well with the way PHP handles "pointers", by storing the name of a variable in another variable."

Which only shows the deficiency, since any such indirect reference should never refer to a name, but to a binding instead.

Re: Some dark corners of C

#162
post #160
post #159

Earlier quoted context omitted.

You're suggesting I write C89 in 2013 just to support MSVC? C89 in which you can't even mix data and code? I think casting void pointers is very much the lesser of two evils. And of course I can use MinGW, but that doesn't mean the majority of Windows developers don't insist on using MSVC.

C++ is not a superset of C[0]. If you want to use MSVC and newer features, then you're not writing C anymore; you're just writing an awkward C++ program. [0] http://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B

Nope, I'm definitely writing C99, but it just so happens that MSVC is happy enough pretending it's C++.

Definitely writing C99 in that I compile the exact same source files with -std=c99 -Wall -pedantic when using a real C compiler.

Re: Some dark corners of C

#163
post #152

Earlier quoted context omitted.

1) On malloc() and free(), right, I was free just to write my own. I should have. At various times since for various reasons, I have just written my own. On your "K&R and other good C references describe their public interface well and that's all you need to know to use them effectively." I want more. By analogy, all you need to drive a car is what you see sitting behind the steering wheel, but I also very much want…

C is not perfect. It has its problems (strings, ++, horrible type syntax, no memory allocation, architecture idiosyncrasies like type width). However, it's reliable, fast, and you can basically memorize the language and compile it by hand if necessary. No other language will reliably run on many systems that fast with that much existing code. anyway, if you think you can prevent bad code by using restrictive language…

"C is not perfect." Yup, it has some "dark corners" or whatever want to call its flaws.

"No other language will reliably run on many systems that fast with that much existing code." Yup, and just such reasons are why at times I used it. It's in effect also why I'm using some of it now although mostly my code is in Visual Basic .NET (VB): The Microsoft VB documentation is fairly clear ('platform invoke' or some such) on how to call C from VB. Well, I have some old Fortran code I want to call from VB, do have the old Watcom Fortran compiler, but do not have details on how to call Fortran from VB. So, I used the old Bell Labs program f2c to convert the Fortran to C, used the Microsoft C compiler to compile and link to a DLL, then call the DLL from VB. And actually it works. And in effect the reason I can do this is what you said: C is so popular, for the reasons you gave, etc., that Microsoft went to the trouble to say how to call C from VB. Microsoft didn't do that for Fortran, PL/I, Algol, Ada, etc. You are correct that the popularity of C is important.

"anyway, if you think you can prevent bad code by using restrictive languages," Right. Each such restriction eliminates only some cases of bad code.

> Any language can be abused. Just don't abuse it, treat your code with respect.

Right. There is "When a program is written, only God and the programmer understand it. Six months later, only God." Well, so that I could read my code six months later, I wrote only easy to read code. So, I would write

n = n + 1

and not the short version, and would never write i+++++j.

> > in my startup I don't want us using a language that permits code like that.

> Well I hope you don't run unix or windows, python or ruby, Firefox, chrome, ie, safari, or opera, or use a smartphone.

You lost me: I'm using VB and find nearly all the syntax to be fine, that is, easy to learn, read, and write. And the main reason I'm not using C# is what it borrowed from C/C++ syntax. I'm using C only when really necessary. Sure, I use Windows and Firefox; if they are written in C/C++, that's their issue. But by staying with VB, I am successful with my goal of

> in my startup I don't want us using a language that permits code like that.

> Also I'm pretty sure j+++++k has undefined behavior so you should be shot if you write it.

As I recall, I actually tried it once, and it compiled and ran as I explained. And, as you explain, if it works in one heavily used C compiler, then it should work the same in all of them. If look at j+++++k, I suspect that it parses according to the BNF just one way with no ambiguity. So, don't have to write, say,

(j++) + (++k)

Re: Some dark corners of C

#164
post #159

Earlier quoted context omitted.

> The problem is, if I want my C code to compile with MSVC, it has to compile as C++ As someone who has compiled a lot of .c files that do not cast void pointers with cl.exe, I'd say no, this is not true... Maybe what you're trying to say is that your code relies on C99 features that are also present in C++? IMO if that's what's holding you back it's much easier to just write C89, maybe with the occasional ifdef, tha…

You're suggesting I write C89 in 2013 just to support MSVC? C89 in which you can't even mix data and code? I think casting void pointers is very much the lesser of two evils. And of course I can use MinGW, but that doesn't mean the majority of Windows developers don't insist on using MSVC.

> C89 in which you can't even mix data and code?

I don't think this is as big a deal as you're making it out to be. Maybe I'm just used to it, but this is one area where I consider the C89 way to be better on stylistic grounds. (I wrote about this on a stack exchange site some time ago: http://programmers.stackexchange.com/questions/75039/why-dec...)

Realistically I don't think C99 is all that revolutionary, and the fact is there are plenty of people writing C89 in 2013. Let's look at some things C99 adds over C89:

* Mixed declarations and code

IMO not a huge deal, for reasons I give above.

* VLAs

A nice feature, but people have been using malloc for similar purposes for ages, so it's hard to say it's any better than "nice to have". I'd also argue that something that leads me to potentially consume arbitrary amounts of stack space based on runtime decisions is probably not universally good, and I'm pretty cautious about using VLAs even when given the choice.

* Last field of a struct can be variable sized array, eg. struct foo { int num_data; int data[]; };

I like this pattern and have used it a lot. Including with VC++ by having a zero-sized array at the end of the struct. This is once place where I use an ifdef to bridge the gaps because recently I noticed LLVM started doing weird things for this if you don't do it the "legit" C99 way.

* Cosmetic issues like // comments

VC++ already supports this in C89 files as a non-portable extension.

* Portable typedefs like uint32_t, etc.

These are also pretty handy. But if you're building with VC++ you're probably using the non-portable Microsoft ones anyway. (I think VC++ 2010 added the C99 versions too.)

* bool type

Again, every platform and even reasonably sized library works around this with a typedef and some macros.

* Slightly different behaviors for some libc functions

I notice this most for snprintf, where the pre-C99 return value [followed by Microsoft] means something different [and better] in C99. Again there are non-portable solutions for this that you can use ifdefs for.

* Named initializers for struct members

Cool feature. I'd hardly say I couldn't live without it, though. The only place I've really seen it used extensively is in the Linux kernel.

* Stuff that no one uses, such as "static" as used in these slides, or complex numbers, or whatever.

Not a problem since these don't see wide use.

Re: Some dark corners of C

#165
post #159

Earlier quoted context omitted.

You're suggesting I write C89 in 2013 just to support MSVC? C89 in which you can't even mix data and code? I think casting void pointers is very much the lesser of two evils. And of course I can use MinGW, but that doesn't mean the majority of Windows developers don't insist on using MSVC.

> C89 in which you can't even mix data and code? I don't think this is as big a deal as you're making it out to be. Maybe I'm just used to it, but this is one area where I consider the C89 way to be better on stylistic grounds. (I wrote about this on a stack exchange site some time ago: http://programmers.stackexchange.com/questions/75039/why-dec... ) Realistically I don't think C99 is all that revolutionary, and the…

Mixed declarations and code are absolutely essential in a modern C programming style. It's all very well to declare things like "your functions should be shorter" or "you shouldn't use temporary variables in macros", but it's just not practical in real world code. In fact, it's so impractical they amended the spec 14 years ago.

Then there are things like the portable integer types - which you decided aren't necessary because everyone is writing Win32 code (???), the designated initializers which I use every day to make my struct initializing code safe from changes in the struct itself, for loops, a consistent bool type ... heck, I could go on all day if you hadn't already dismissed the rest of the standard as "stuff that no one uses".

I really don't mean to come across as aggressive, but this kind of ignorant and dismissive attitude is so rampant in the tech scene at the moment. And it's getting us nowhere.

Re: Some dark corners of C

#166

Earlier quoted context omitted.

1) malloc() and free() are just library calls, they're not first class citizens of the language. K&R and other good C references describe their public interface well and that's all you need to know to use them effectively. The public interface encapsulates the implementation details, good software engineering in my book. 2) Usage of the stack reflects C's low-level, high performance "portable assembler" roots. Choosi…

1) On malloc() and free(), right, I was free just to write my own. I should have. At various times since for various reasons, I have just written my own. On your "K&R and other good C references describe their public interface well and that's all you need to know to use them effectively." I want more. By analogy, all you need to drive a car is what you see sitting behind the steering wheel, but I also very much want…

Well I certainly salute your passion. I am not nearly dedicated enough to go through this point by point. The stack issue comes down to this; C uses the assembly (i.e. machine) stack. It is an almost ridiculously simple mechanism ideally suited to pass parameters and allocate 'automatic' (yes this is a C term too) data. Avoid large aggregates and arrays on the stack because stack space is limited. Providing you adopt a conservative approach, you never have to worry, 90% of a 2K byte stack in a small embedded system is typically safety factor/headroom.

My personal view is that C offers a perfect tradeoff between simplicity and capability, it has a magical quality that has made it the most important single computer language for nearly 40 years and on into the forseeable future. Increasingly its importance is as a layer that more programmer friendly technology sits upon, but it's no less important for that.

I've read that the difference between chess and go (the oriental game, not golang) is that on Alpha Centauri if little green men play a game that resembles chess, they will almost certainly play a game identical to go. Go is simple enough it is almost inevitable. For me it's almost the same thing (I stress almost), with computer languages and C.

One final point; C syntax is ultimately a matter of taste. If you find this to be a completely obvious, correct and straightforward way of doing a non-overlapping C string copy;

  while( *src )
    *dst++ = *src++;
  *dst = '\0';
Then you 'get' C. If you find it a confusing monstrosity, maybe C isn't your language.

Re: Some dark corners of C

#167
post #165

Earlier quoted context omitted.

> C89 in which you can't even mix data and code? I don't think this is as big a deal as you're making it out to be. Maybe I'm just used to it, but this is one area where I consider the C89 way to be better on stylistic grounds. (I wrote about this on a stack exchange site some time ago: http://programmers.stackexchange.com/questions/75039/why-dec... ) Realistically I don't think C99 is all that revolutionary, and the…

Mixed declarations and code are absolutely essential in a modern C programming style. It's all very well to declare things like "your functions should be shorter" or "you shouldn't use temporary variables in macros", but it's just not practical in real world code. In fact, it's so impractical they amended the spec 14 years ago . Then there are things like the portable integer types - which you decided aren't necessar…

Not meaning you to make you feel like you're inching towards the aggressive, I just feel like the distance between C89 and C99 is not a very big one. Even in C99 I tend to use some perhaps conservative style choices, and in this view the C99 stuff really does seem like "niceties" rather than essentials. The difference between C and C++ feels much bigger to me.

And then after all, you initially explained the need for this in terms of Windows-specific code (or at least that's how I read it), so that's the angle I took...

Re: Some dark corners of C

#168
post #139

Earlier quoted context omitted.

Um ... but K&R did. Chapter 8, section 7, "A Storage Allocator". Yes, it's simple, but it's there.

Yup, there's a version of each of malloc() and free() there. Maybe that's what was being used in the common versions of C. If so, then for whatever reason I missed out on that. I kept seeing in the book where they kept saying that malloc() allocated storage in the 'heap' without being clear on what they meant by a 'heap' although in this thread is an explanation that 'heap' was also used in Algol 68. Whatever, when t…

Did you ever check the documentation for the C compiler you were using? Every C compiler I've used have always come with extensive documentation, which included documentation about the standard C calls, like malloc() and free().

Even today, the GNU C compiler (which is what I mostly use) has non-standard extensions to malloc()/free() that allow you to obtain information that is otherwise not mentioned in the C Standard (GNU defines mtrace() and malloc_hook, for instance, which trace each allocation, and allow you to peek into malloc).

From your descriptions, it sounds like you wrote C back in the 70s or 80s. It's changed a bit since then.

Re: Some dark corners of C

#169

Earlier quoted context omitted.

1) On malloc() and free(), right, I was free just to write my own. I should have. At various times since for various reasons, I have just written my own. On your "K&R and other good C references describe their public interface well and that's all you need to know to use them effectively." I want more. By analogy, all you need to drive a car is what you see sitting behind the steering wheel, but I also very much want…

Well I certainly salute your passion. I am not nearly dedicated enough to go through this point by point. The stack issue comes down to this; C uses the assembly (i.e. machine) stack. It is an almost ridiculously simple mechanism ideally suited to pass parameters and allocate 'automatic' (yes this is a C term too) data. Avoid large aggregates and arrays on the stack because stack space is limited. Providing you adopt…

So, with just 2KB, that machine stack is not the stack (of dynamic descendantcy', that is, the conceptual stack of routines called but not yet returned) for automatic storage. Good to know. So, if only pass pointers or 'element' variables, then a 2KB stack for parameter lists should be okay for small to medium programs unless the programmer actually believed his computer science course that said that recursive routines were good things!

Yes, for your code example, I 'get it'! It's cute! No doubt it's cute.

So, to get 'full credit', dst and src are 'strings', that is, essentially just pointers. Since C pointers have a data type with a length, here the data type of these pointers is byte, or character, or some such with length 1 byte.

Starting off, since src points to a C string that obeys the C standard of null termination, we know that the string has exactly 1 null byte, its last byte. So, for any byte except the last one, it is not null. So, if the string has length more than 1, then as we enter the While, src points to the first byte of the string (or at least the first byte we want to copy); * src is that byte; and * src is not null, that is, is not 0, that is, tests as True for the While (need to know that 0 tests as false and anything else tests as true). So we execute the statement following the While.

That statement says copy byte * src to byte * dst and, then, before considering the semi-colon and before collecting $100, increment both src and dst by the length of their data types, that is, by 1 byte. So, now src points to the next byte in its string and dst points to the target of the next byte in its string. Then we return to the While and do its test again. If we have more bytes to move, then we just rinse and repeat the above. Else src points to the last byte of its string which has to be the null byte so that the byte itself, * src, is the null byte, that is, 0, that is, False in the While statement. Then we leave the While statment, that is, move past its semi-colon. So, net, the While statement moves all the non null bytes we want moved. Of course when get to the next statement, dst points to the last byte of its string, that is, the byte that is to be its null byte (just why that byte is not null already is possibly an issue). At any rate, we want that last byte to be the null byte, so that last statement so assigns that last byte.

For more, 'src' abbreviates 'source' and 'dst' abbreviates 'destination'.

So, yes, it's possible to describe this stuff in English.

So, there's a problem, a significant problem: I 'documented' your code. Okay, but your code is for a string copy. There should be some documentation, but it should be in documentation of a string operation for the language. That is, even just for the documentation, the strings and the copy operation need to be 'abstracted' to a higher level where they can be documented and learned there and set aside the need to document in the code.

Of course, I would write such a copy loop, assuming I wanted to use a loop, using Fortran's Do-Continue, PL/I's Do-End, VB's For-Next. I don't recall the Algol syntax. The PL/I syntax is the same as in Rexx which I use heavily.

Of course in PL/I and VB, I would use the substring function instead of a loop.

And I would fear that too much usage of syntax as sparse as this example here would be more error prone. And for more complicated operations, I would fear that neither God nor the programmer understood the code. I can understand that on some processors with some compilers, that C syntax could lead to faster code, but I'm not thrilled about digging into x86 assembler enough to be sure. Also now it's tough to know what fast code is due to out of order execution, speculative execution, parallel execution, pipelines, three levels of cache, the cache set associative, and cache line invalidates when have several cores. But, computers are so fast now I don't much care, and if I did care I would notice that making such code faster would not make the code in the runtime library or the operating system faster and, thus, might not be able to do much for actual performance of my application.

I don't really mind your example; it's actually not sparse enough to be a serious problem. But I'm not thrilled by the example because I don't take pleasure in that clever sparsity and, again, I'm afraid that it could result in bugs that could hurt my business.

Then there's the issue, I regard as significant, that with that code the C compiler is very short on the 'meaning' of what is being done. Or, you and I can look, read, guess, and agree that we are doing a copy of the tail of one C string to another (or possibly the same if initially src equals dst) string. Okay, you and I can guess that. But the poor compiler can't, and if it tries then I might get torqued when in that loop actually I'm not working with C strings but doing something else. So, then, the compiler will have a heck of a time checking string lengths and not writing on the wrong storage. So, I'd rather have strings as a higher level construct, than just a pointer to some storage from malloc(), so that the language can help me debug my code. I've programmed so many errors in array bounds and string lengths that I don't want to be without some good checking at runtime, at least in some 'debug' mode.

Next, for being 'fast', on at least IBM 360/70, etc. computers, actually that code sample would be slow! Why? Because that instruction set has a single instruction to copy all the bytes in a range of sequential virtual addresses. So, if the compiler knows it is working with a string and knows it is compiling for that instruction set, then it can replace the whole loop with just one instruction.

There was an old remark in the IBM PL/I program logic manual on execution speed: People could complain about PL/I being slower than Fortran. But if PL/I were used at all carefully, then it was faster than Fortran, and one of the main reasons was that PL/I then but not Fortran had strings in the language. So, Fortran programmers wrote collections of string handling functions/subroutines, and the internal logic was much as in your example, move one byte at a time. And as in the standard C library, do this by calling a function/subroutine with its overhead. PL/I's compiled code for strings was in-line and blew the doors off anything in Fortran. For today, and for similar reasons, Visual Basic .NET has a chance to be faster in string manipulations than C code such as in your example. Further, it's super tough to do something with VB strings that would mess up memory.

I don't find your example "a confusing monstrosity", but I greatly prefer to bet my business on VB instead of C/C++.

Re: Some dark corners of C

#170
post #168

Earlier quoted context omitted.

Yup, there's a version of each of malloc() and free() there. Maybe that's what was being used in the common versions of C. If so, then for whatever reason I missed out on that. I kept seeing in the book where they kept saying that malloc() allocated storage in the 'heap' without being clear on what they meant by a 'heap' although in this thread is an explanation that 'heap' was also used in Algol 68. Whatever, when t…

Did you ever check the documentation for the C compiler you were using? Every C compiler I've used have always come with extensive documentation, which included documentation about the standard C calls, like malloc() and free(). Even today, the GNU C compiler (which is what I mostly use) has non-standard extensions to malloc()/free() that allow you to obtain information that is otherwise not mentioned in the C Standa…

I wrote C in the 1990s on IBM mainframes, PC/DOS, and OS/2. I used some IBM mainframe, Microsoft, and OS/2 documentation. At one time I wrote some C code callable from PL/I for the TCP/IP calls, available to C but not to PL/I. I wrote some little utilities in C on OS/2, e.g., for sorting files of strings. Recently I wrote a grand, very carefully considered solver for Ax = b where A is a m x n matrix and x and b are m x 1. So, the A need not be square and if square need not be non-singular. I was fairly careful about numerical accuracy, etc.

For the C documentation, I recall only one point: Due to all the different addressing options on x86, the Microsoft C compiler had a crucial but nearly secret command line switch we needed. I found the switch only by some strange efforts. It was not in the documentation. None of the documentation I had was much beyond just K&R.

Post reply on HN