3 ms·
> - pointers are type checked, not sure what you mean here They weren't here with Borland. Maybe it was one of the many optional compiler switches, so I'll tak
by jstimpfle 8y ago
> - pointers are type checked, not sure what you mean here
They weren't here with Borland. Maybe it was one of the many optional compiler switches, so I'll take that back.
> No macros is plus, the large majority at ISO C++ is creating safer alternatives for each use case
That's just wrong. Learn how to use the tool and use it when it makes sense. There are a LOT of situations where the easiest for maintainability by far is to abstract at the syntactic level. The Rust guys acknowledge this as well. Even the Haskell compiler (GHC) has a C-style preprocessor built-in. For example, I use macros for data definitions, to create identifiers, to create strings from identifiers, to automatically insert additional function call arguments like size information or source code location...
> that extra mile
You mean 100x - 1000x in speed?
> Thing is, for 99% of most applications that is largely unnecessary.
Your initial comment was explicitly about performance. And I disagree that 99% of applications should accept a 100x - 1000x decrease in speed (and harder maintenance by far, if you ask me!) (corollary: less purity and joy in programming, by far), or even a 10x for that matter, just to get some more safety. I mean, safety is nice and all, but it's not all the reasons why I'm doing this. YMMV.
EDIT: I now understand that you mean "99% of most applications", while I read it "99% of (or most) applications". I disagree with the implicit statement that you should write 99% in a "safe" language and the rest in a systems language. It is well known that you can never easily know where the bottleneck is or where the next will be. And it is well known that it is very laborious to accomodate multiple languages, or incompatible code styles, in a single project. (I've also heard some negative stories about integrated scripting languages, for example from the games industry, but I don't work there...)
And in the end, speed is actually not the primary reason why I prefer C...
- pjmlp 8y agoMacros can be easily replaced by other means, that is what Java and .NET do via annotations and compiler plugins. Rust macros are tree based and integrated into the compiler, not a text substitution tool running before the compiler, which cannot reason about what is happening. I don't get where that 100x - 1000x factor comes from, most compiled languages aren't that slower than C, specially if you restrict to ISO C. If C extensions are allowed in the benchmarks game, then those other languages also have a few tricks up their sleeves. For example, I can disable all checks in Ada via Unchecked_Unsafe pragmas and in the end there will hardly any difference to generated C code. The big difference is that I am only doing that in that function or package that is required to run like a 1500cc motorbike at full throttle to win the benchmark game, while everything else in the code is happy to run under allowed speed limits.
- jstimpfle 8y ago> Macros can be easily replaced by other means, that is what Java and .NET do via annotations and compiler plugins. There are cases where text substitution is the right thing to do. How do you do the things I mentioned, for instance? Java in particular is an infamous example, requiring lots of hard to maintainable boilerplate. Tooling helps in some cases to write it, but can't help reading it, right? Some examples from my current code #define MAKE(x, y, z) [x] = { #x, y, z } #define MSG_AT_EXPR(...) _msg_at_expr(__FILE__, __LINE__, __VA_ARGS__) #define PARSE_LOG() \ if (doDebug) \ MSG_AT(lvl_debug, currentFile, currentOffset, \ "%s()\n", __func__); #define BUF_RESERVE(buf, alloc, cnt) \ _buf_reserve((void**)(buf), (alloc), (cnt), sizeof *(buf), 0, \ __FILE__, __LINE__); #define CLEAR(x) mem_fill(&(x), 0, sizeof (x)) #define SORT(a, n, cmp) sort_array(a, n, sizeof *(a), cmp) #define RESIZE_GLOBAL_BUFFER(bufname, nelems) \ _resize_global_buffer(BUFFER_##bufname, (nelems), 0) In Delphi I've had to manually write all these expansions, resulting in less maintainable code. Go look in the linux kernel, I'm sure there are tons of examples that you'd be hard pressed to replace by a cleaner or safer specialized means. > I don't get where that 100x - 1000x factor comes from, most compiled languages aren't that slower than C, specially if you restrict to ISO C. It's not so much about the language, but what you do with it and how you structure your code. Or, actually, how you structure the data. OOP culture is bad for performance. If I were to chose the single best resource for this kind of argument, that would be Mike Acton's talk from CppCon 2014 on youtube. If you want to watch that. Note that I'm not his fanboy. These are experiences I've made on my own to a large degree. And the arguments apply just as well to maintainability if you ask me. > The big difference is that I am only doing that in that function or package that is required to run like a 1500cc motorbike at full throttle to win the benchmark game, while everything else in the code is happy to run under allowed speed limits. And so the rebuttal is: No. If your code is full of OOP objects you can micro-optimize a certain function like crazy, but the data layout and the order of processing are still wrong. To give another anecdata, for my bachelor's thesis I had to write a SAT solver for clauses of length <= 3 in Java. I modeled clauses as POD objects holding 3 integers (the 2nd and 3rd of which could be -1). My program could do about 10M clauses before memory was getting tight and it was doing only GC for at least a minute before it would finally die. Note that all objects are GC'ed reference types in Java (as you probably know). I then converted it to highly unidiomatic Java by allocating 3 columns of unboxed integers of length #clauses, instead of allocating #clauses POD objects. The object overhead went away, so I could do about twice as many clauses before memory was used up. And when it was used up, since there was basically no GC overhead, the program died immediately (after a few seconds of processing, without a minute of GC). The downside was that maintainability was drastically worse since I was using only the most primitive building blocks of Java, and none of its "features". If that had been in C, I could have just stayed with the first approach, since C has only "value types". It would have been performant from the start. C would have yielded a more maintainable program since I would not have had to fight the language. I could also have chosen the second approach, and it would have been easier to write and read than the Java code (which required tons of boilerplate).