I highly question yours... you sound crazy calling this better. The Apple Watch isn't the most beautiful designed gadget but you are making a really silly statement. It's honestly blowing my mind how you think this.
You assume it's very little pay off. You never really know what Apple would bring to the table if they did. Honestly I still can't believe they are trying to make a car. If they do it's very likely they know exactly what there doing.
I don't think Einstein has been proven wrong. He just said we don't have a full understanding of quantum physics. God playing dice hasn't won till this day. So far its a black box we haven't cracked.
In a certain way, it is. But for a lot of things C and (C++) is still necessary, and the fact that C is not safe is a requirement. Can you imagine the Linux kernel not being written in C? Just imagine all the overhead if the Linux kernel has to perform overflow checking after every internal integer increment.
Maybe some day Rust will be a good candidate, but that day hasn't come yet.
I'd say that in terms of seats sold, Borland and Microsoft had more to do with it. Maybe first one then the other. But 'C' was the semi-official language of the old DOS PC world from 1985 until Linux happened. It's somewhat tragic, because Modula-2 and Ada toolchains existed, but were harder to use than 'C' for performance and memory constraint reasons ( not to mention the toolchain costs )
>Borland started out with Pascal, but went to 'C'.
As someone that used all their products before they became Imprise or how it was called, I never saw this happening.
Even nowadays C++ Builder actually uses the Delphi framework.
EDIT: Just wanted to add that in my part of the planet, it was all about Assembly for systems programming, games and real applications. Turbo Pascal for general purpose applications without much performace issues and CLIPPER for the typical CRUD applications.
Well I mean it kind of is. C's my favorite language but it's like a machete, simple enough, fairly easy to use and useful in a lot of places but if you don't know how to use it properly it's very easy to cut yourself with.
It's not the 80s anymore, there area many cases where there are much better alternatives to C.
But it's still a language you need for certain things, like responsiveness or real time applications. Like linus torvalds said it, C is like a more comprehensive assembly generator than anything else.
Having such a language allows you to change every little boring details to do exactly what you want. It has its use, and it's surely not intended to be mainstream as of today. But there are many cases where C comes back as a constant common denominator.
It's sure that it's kinda shitty that there's nothing really better than a language designed in the 70s, but I guess you could either blame language designers for not making anything better. There also are business/political issues and preferences for certain things that comes into play.
All in all, C just seems to be pretty good when you want to have code that works well with systems. Maybe there's just inertia as to where computer development is headed, which might be heavy related to C.
All I wish, is someday to be able to learn some more "virtuous" language like haskell, scheme, or smalltalk.
You may want to look up the age of those "virtuous" languages you listed. While it's fun to bash C, you could drive a damn yacht through the undefined behavior in Scheme. High-level languages are not immune here.
The article doesn't make much sense at times: Dereferencing a NULL Pointer: >>>contrary to popular belief<<<, dereferencing a null pointer in C is undefined.
I guess C programmers are not counted in the "popular" group.
I don't think it's wrong at all. I suspect if you asked a large number of even moderately competent C programmers, you'd find quite a lot of them believe it's defined to trap. That's exactly what the rest of it is talking about. Never mind the issue raised of how it potentially affects optimization, which I think very few C programmers would actually be able to speak to.
That, of course, would be a leading question. Most people would probably tend to assume that you're asking "in the environment in which we're programming, what happens if you dereference NULL?" which is not what you meant.
I didn't poll my coworkers either, but at least I think the answers would be different if I asked "what does C say should happen when a NULL pointer is dereferenced?".
Perhaps that's just wishful thinking on my behalf, though. :)
I don't think that's particularly wishful thinking, however, your alternative phrasing could be seen as equally, if not more, leading.
When quizzed on the behaviour of a C program I would expect a careful and experienced C programmer to consider the behaviour described by the standard (which standard?); unspecified and implementation defined behaviours; and deviations from the standard in both the compiler and the environment. Often I'd expect the answer to be "it depends".
I think, once you unanimously conclude that something should not be done, that people don't really care _why_ it shouldn't be done until someone tries to suggest you should do it.
For example, we know not to dereference null pointers. It is pretty much never correct to do so, and it's hard to imagine otherwise. What does it matter what C says should happen when you do it? You're never going to intentionally do it, so it becomes an incongruous hypothetical. Like "what would happen if you were never born?" The answer is useless because the question is inherently flawed.
I'll take the downvote as a request for elaboration. In C, you dereference a pointer. This typically translates to machine code that loads memory at an address. The translation is so direct that we tend to think of them as identical, i.e. dereferencing a pointer is loading memory at an address. But this is not so; first of all, nothing says pointers have to be implemented as memory addresses in the first place, and even if you do implement them that way (as virtually every C implementation does), not every pointer dereference has to generate a load instruction. That's not just the case for optimizing out NULL dereferences. Optimizations mean that in general, an expression p can have the memory load optimized away if the value at that location is already known from a previous computation (say, because you just assigned a value there, or you read the value a bit earlier and it's still available in a register).
In C, you deference a pointer. In assembly or machine code, you load memory at an address. The two don't have to happen at the same time, though, and just because you don't end up loading memory at an address doesn't mean you didn't dereference a pointer.
In short:
*(char *)NULL
This always dereferences NULL, even if the compiler optimizes out the statement entirely.
Good point. Some embedded environments will have different behavior. I don't recall what happened under MS DOS or if MS compiler provided an debug option.
A completely unrepresentative poll among two C++ programmers showed that 50% thought it was a segfault, whereas the other 50% suggested it could be undefined behavior, deducing that from the knowledge that the behavior is different in Linux userspace and kernel.
Although I wouldn't call those two typical C++ programmers. They do security CTFs for fun, so they might have a broader knowledge about exploitable behaviors in C/C++ code.
For a while you were actually able to mmap the memory at address NULL under linux, which is usable for exploiting (you can pass arbitrary data to kernel code that you can bring to dereference null pointers)
I recommend reading the articles linked elsewhere in this thread:
https://news.ycombinator.com/item?id=8879948
A lot of this is C compiler writers using the spec to perform counterintuitive "optimizations" (with dubious performance value), and forgetting that they're writing software for C programmers to use, not trying to win a pedantry war with them.
Yes. Hash table, linked list and a graph of some sort are those fundamental datastructures that everyone should have implemented at least once by themselves. IMO, the core to excellent software engineering his having a lucid intuition about fundamental things such as those.
Mainly because they are simple, and lot of problems are solvable by using them without resorting to Someone Elses Gigantic Platform Framework.
In my experience, there's strong correlation between programmers that don't know how to implement basic data structures and programmers that make poor decisions when picking up one from a collections library. The ones that know that they don't know at least stick to fixed sized arrays and pay the performance price.
So, no... and yes. You can call yourself a software engineer without knowing any of this stuff, but you will be a better one if you do.
Learning what it is and how it works is necessary. From there, any remotely competent programmer should be able to quickly and easily create one.
Programming isn't about learning how to create specific things. But learning how to create specific things can help you gain the intuition necessary to create other things.
In C.. Yes.. it's a basic data type you'll probably find yourself using quite a bit. If you program in some other language that already provides hashtable functions/types, then maybe you can get by with just knowing how it works so you can figure out when to use it.
I think more languages should adopt Swift and Objective-C's automatic reference counting. The only downside to ARC is retain cycle's which rarely happen in my experiance. A deterministic object life cycle just feels right.
Automatic reference counting has its own issues: 1) allocating short-lived objects is not cheap, because they call down into an underlying malloc/free; 2) as a consequence of (1), throughput is lower; 3) getting acceptable overhead for manipulating references in the heap/stack requires compiler optimizations to elide those reference counts, which adds a layer of complexity to your mental model of the program; 4) implementing the reference-count operating in a multi-core system requires pretty heavy-weight atomic primitives, and race conditions can result in incorrect reference counts.[1]
One of the more interesting avenues of research, especially in mobile devices where you don't want to pay the power cost of having a 2x larger heap your live size, are various hybrids of garbage collection and reference counting. A neat, easy to understand one is: http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rcix-oops....
Also interesting is what you can do when you have more information about what pointers can point to (as in Rust). It's not so much that reference counting in Rust is cheap, but that the language offers a lot of tools to let you avoid reference counted pointers in the first place, in favor of references with static lifetime guarantees.
[1] What Swift or Obj-C do when you overwrite a pointer-valued field is to do a objc_release() for the old pointer, and a objc_retain() for the new pointer. If two threads write to a field at the same time, you can corrupt the reference count (even if the objc_release()/objc_retain() operations are themselves atomic!) As far as I know, Apple's obj-c runtime does not attempt to handle this situation. See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#o....
I prefer well known memory lifetimes, but I'm undecided about ARC (or somewhat equivalently: std::shared_ptr, etc.). It can be a little confusing and overwrought at times, and I don't think the lifetimes are as clear as they could be.
IMO, more languages shouldn't assume that there is a single memory allocator. That's one of the worst assumptions I see in systems languages -- even C++ (before C++11) got this entirely wrong. Swift gets this wrong, Go gets this wrong. Rust is probably a little better because it actually has a static idea of memory scope, but I haven't seen a way to swap out the memory allocator in various contexts.
Most projects I've been involved with have used region/arena allocators. Not only do you mostly avoid the non-determism of your average GC, but you avoid the hassle of fine grained reference counting (in most cases). This relies on you choosing the scopes for your regions appropriately, but there usually is a clear scope to attach things to (e.g. frames, iteration of an event loop, etc.).
Yes, but (according to the spec) it was stateless -- which made it worthless for the purposes I'm describing. The original purpose was to support custom static strategies for allocation (e.g. i86 near/far pointers or one memory pool for one kind of object for the whole program), not dynamic memory pools and arenas. C++11 fixed that, which is why I mentioned it.
That said, by C++03, most STL implementations supported stateful allocators (and that's what I've used when I've had to use C++), but the standard took a while to catch up.
Sadly though I haven't been able to find any mention of if and when it will be realized. But the sentiment - from what I've gleaned from discussions on it - seems to be that something like that should/need to happen.
As far as I know ARC (like manual memory managenent) can lead to release cascades that can also cause application delays in games eg. Something you have to watch for. Malloc() and free() and equivalents do quite a bit of work under the hood (check today's linked tcmalloc article for example) that takes time.
Release cascades can be a big problem even with plain old RAII in C++. I once diagnosed a performance issue in a C++ network server where the server listening thread would sometimes temporarily hang when clients disconnected. The culprit turned out to be the destructor of a large std::map that directly and indirectly accounted for tens of millions of heap-allocated objects that had to be individually destructed and freed. It's very rare for destructors to have intentional global side effects, so this kind of work could almost always have been done asynchronously, at least in principle.
An unfortunately common symptom of large C++ applications is that they take forever to shut down because they insist on calling destructors on everything in the known universe. In reality, most applications only have a tiny handful of resources that you truly have to release yourself at shut down, and memory certainly is not one of them.
If the mass objects have trivial (ignorable) destructors and don't own any heap-allocated subobjects then it's not a problem. You can have top-level objects like levels or documents or sessions own everything directly and indirectly under them and allocate those things out of a private heap that can ideally be reused but otherwise be bulk freed in a few calls. Unfortunately, the standard C++ philosophy around RAII and value semantics works against this. Since there is something to be said for the elegance and upfront convenience of the value semantics approach, it comes down to a trade-off.
So, if I design a language, how design it well? I suspect is easy to kill all values (ints, str, etc) and arrays/list of it. But how know what is a "don't own any heap-allocated subobject" (sorry to ask if this is obvious, I have not deep experience in this matters)?
Is not circular references the real problem? And what when the object reference a resource like a file/handle/database/etc?
So, could be good idea to mark objects like this (maybe in separated areas of memory?):
Instant kill: Ints, Strs, Bools, Array of all of this
Safe destructors: If it hold a resource (file, handle)
But don't know what to do for objects like Customer.Orders = List<Orders>[Order1.Customer]
This is not necessarily a language-level decision (except for GC, it helps in this case spread out the deallocations when using incremental GC).
You just have to be aware what happens in the destructor when you manually free an object. Libraries can make knowing this more difficult.
I guess you could build incremental alloc/release pools (even with reuse) or such things but it comes down to being aware of the problem as described and avoiding cascaded releases.
And to be honest this is not a super common problem but it can happen.
There is also this paper by Bacon et al that develops a framework that shows the relationship between refcount and tracing GC (duals of each other) plus the various optimizations of the two: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.143....
There are certainly classes of mobile apps that need the guarantees of lower/deterministic memory usage. In my experience, that is not the common app being written however. These concerns seem largely like fantasies not backed up by any concrete evidence for your every-day twitter client/mail app/weather/whatever. Generic list-based apps simply do not need to be acting as if you need to squeeze every ounce out of the processor/ram anymore. They would instead benefit much more from not having to worry about whether self in the closure you're creating should be weak or not. Especially when you consider that many (most?) of the apps on the App Store that are actually pushing things to the limit -- games -- are running in a (old) GC environment (C# in Unity).
I would be quite surprised if most App Store games were created with Unity - it's expensive and (comparatively) cumbersome to develop in, especially if you're just using 2D graphics (even with the fancy new Unity2D stuff). I can't find any stats on mobile game engine use though, which is a shame because I think it would be interesting to know.
When creating Unity3D games, you have to be reasonably careful about garbage collection - lots of nasty performance dips result if you assume that it just works. Generally you have to cobble together a mixture of object pools and statements attempting to force collection at a convenient point in the game flow amongst other things. It would be quite nice if you could turn it off for specific portions of code :)
Unity uses an older Mono (2.10 I believe) which has a poor GC that's not even deterministic, so you can easily suffer memory leaks. It is also not generational. Newer versions of Mono come with a much better GC.
Ref counting is not deterministic. First of all because while looping on an atomic reference is non blocking, it is not wait free. Malloc/free are not deterministic either, can be quite expensive and while real-time implementations exist, that's not the malloc/free you end up using. With a generational GC allocating new objects involves just incrementing a pointer, deallocating short lived objects happens in bulk, so the cost for short-lived objects is similar to stack allocation. With ref counting memory gets fragmented, apps like Firefox have been suffering for years from fragmentation and for Firefox there was a sustained and very significant effort to fix it.
This can only be solved by either doing stack allocation or by building object pools. And this is for people that know what they are doing.
Rust is the only language (I know) that tries solving this with the ownership concept in the language, but then Rust will have problems in implementing immutable data structures that can be shared amongst threads, data structures which are doing structural sharing, so people will expect reads to scale, except there will be non-obvious contention happening due to usage of reference counting.
The latency in state of the art mainstream GCs is also NOT variable. Good garbage collectors allow you to control the max latency and frequency of STW pauses. That is not the issue for real-time requirements - the issue with real-time being that STW is unacceptable. But so is reference counting.
On the memory requirements, I don't buy it. My Android phone has more memory and more CPU capacity than my computer did 7 years ago. And I'm not seeing a difference in behavior to my iPad. Surely for games it pretty bad to drop frames, but most apps are not games and games are using highly optimized engines built in C++anyway.
I do agree with this critique. Object allocation patterns that suck down space in a GC'ed system equally abuse a refcounting system, just you're paying in CPU time rather than memory.
What algorithm are you referring to when you say "better throughput at the expense of significantly increased memory usage"?
Also, why is unpredictable latency acceptable for you on servers but not phones? Wouldn't a latency spike on remote requests from an application degrade user experience just as much as if that latency were localized to the phone?
> What algorithm are you referring to when you say "better throughput at the expense of significantly increased memory usage"?
Here's a discussion of the particular paper behind that statement: [1]
I'd also like to recommend the paper "A Unified Theory of Garbage Collection" [2] that breaks down the divide between GC and refcounting. There is a lot of gray area between tracing GC and refcounting. You can make different tradeoff decisions in different parts of your collection algorithm. But fundamentally it breaks down to time-space tradeoffs—you want to save time and get throughput, you're gonna eat some extra space.
> Also, why is unpredictable latency acceptable for you on servers but not phones? Wouldn't a latency spike on remote requests from an application degrade user experience just as much as if that latency were localized to the phone?
We as developers make UI efforts to mitigate network unreliability (fallacy #1 of distributed computing: the network is reliable) so it's ok if a server is being temporarily shitty. It's a lot harder to keep responsive, smooth UI behavior in the face of dropped frames and long GC pauses.
This is really hard for me to explain and I don't want people to think I'm being ignorant. I've tried to think deeply about other people's ideas. Sometimes you have to experience a problem to really understand why the idea is good.
I still feel that 80% of these startup ideas are really bad. Maybe there is something wrong in the way I look at them. I want to join a startup here in Santa Monica but so many people like to work on garbage. Can someone explain to me what is wrong with my view ?
If you feel it's garbage, don't work on it. :-) Many startups fail because of execution problems, many fail because they are too early, many fail because they are too late. And yes, some fail because the idea doesn't look good. There is no reason to waste time on the 80% (or 99%) of ideas that don't look good.
But.... Many great ideas didn't look so hot at the time. Instagram? Does the world need one more startup doing photos? Snapchat? Text that doesn't stick around? Really? Of course hindsight corrects us.
At present it's a bunch of programs that run on UNIX systems. I guess the Hoon interpreter is a C program that uses readline, talks to the network over sockets and the like. But everything on top is quite self-contained and in principle one might remove the layers underneath.
But I'm not sure that's necessarily the aim. As I understand the project's aims (and I'm not involved, I just read all the docs a few months back), it's more important that the Internet of the future is not a tangled mess of technologies that are insecure on different layers. Then it doesn't matter so what OS people's local machines are running.
I hope the essay at the URL below is still relevant, it seems to describe the aims rather well. "The result: Martian code, as we know it today. Not enormous and horrible - tiny and diamond-perfect."
C#'s equivalent of GCD is, very roughly, Task. You can create tasks that compute results on the thread pool, give them continuations to run after, etc.
Async/await is an abstraction on top of tasks, where you write code with do-while loops and try-catch and it gets translated into GCD-esque continuation-using code. It lets you write the asynchronous code in an imperative style, so it looks more like the rest of the code you write in C#.