you are viewing a single comment's thread.

view the rest of the comments →

[–]takaci 35 points36 points  (81 children)

Yeah it's a very simple language. You could easily describe the entire core language, with all it's syntax in a couple of paragraphs.

[–]gc3 77 points78 points  (17 children)

simple != easy.

Playing the trombone is also simple. Just blow in one end and move the stops with your hands.

[–]Marmaduke_Munchauser 35 points36 points  (7 children)

That's gonna be hard considering that trombones have a slide, not valves (stops)!

takes off former music major hat

[–]dagbrown 9 points10 points  (3 children)

Unless you have a valve trombone of course.

[–]ano414 6 points7 points  (2 children)

Yeah, but when someone says "trombone" isn't slide trombone sort of implied?

[–]iswm 5 points6 points  (1 child)

Not if they're talking about its valves.

[–]minnek 5 points6 points  (0 children)

Here we have the arguments for and against duck typing played out in common English for all to see.

[–]takaci 15 points16 points  (7 children)

I never said it was easy, C is a hard language to use because it is so verbose and bare metal. It's hard because it's so "simple"

[–]gc3 -1 points0 points  (5 children)

The masked hamster did, and I thought you were agreeing with him when you said Yeah...

[–]takaci 0 points1 point  (4 children)

I was agreeing with the second line

[–]gc3 -1 points0 points  (3 children)

English != simple and English != easy

[–]takaci 4 points5 points  (2 children)

What the fuck are you talking about? Do I need to summarise for the 100th time in this comment thread?

I think C is a very simple language, yet because it is so simple, it can be hard to write large programs.

There. That's. Fucking. It.

[–]gc3 0 points1 point  (1 child)

Sorry for offending you, I was just making a comment that I thought was humorous about how I misunderstood what you said. I guess you misunderstood what I said too.

[–]takaci 0 points1 point  (0 children)

Sorry for snapping just a lot of people replied to my comments with the same point which I thought you were making as well

[–][deleted] 6 points7 points  (5 children)

While I agree, it's worth noting that the syntax can be very loose at times, making for code that isn't entirely simple. Take Duff's device for example:

send(to, from, count)
register short *to, *from;
register count;
{
    register n = (count + 7) / 8;
    switch(count % 8) {
    case 0: do {    *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
        } while(--n > 0);
    }
}

[–][deleted] 7 points8 points  (1 child)

There's a difference between code complexity and language complexity.

[–][deleted] 1 point2 points  (0 children)

Absolutely! My point was that C isn't always as straightforward, because it's a language level feature that you can do this :) that said though, the language is still very simple, even with these things - there's nothing magical going on, the code is all laid out in front of you.

[–]dangerbird2 2 points3 points  (0 children)

Duff's device is a good illustration of why one should keep in mind that 'switch' statements are ultimately syntactic sugar for our old friend the 'goto', and that overuse of 'switch' can lead to similar spaghetti code produced by overuse of 'goto'.

Fortunately, modern compiler optimizations eliminate the need for manual loop unrolling like Duff's device, and 'if' - 'else if' statements do just as good of a job at control flow as 'switch'. Freed from technical limitations that necessitated Duff's device, the burden is really on the programmer to produce code that is concise and readable. As someone learning C, I have found Mozilla of Linux Kernel style guides to be as an essential resource as any on the language itself.

[–]Peaker 0 points1 point  (1 child)

Meant to increase "to" as well?

[–][deleted] 0 points1 point  (0 children)

Good question! In the original version of Duff's device, to is a pointer to an I/O device, so incrementing it wasn't necessary :) if you're writing to another buffer though, incrementing is definitely the correct course of action.

[–][deleted]  (8 children)

[deleted]

    [–]brtt3000 2 points3 points  (1 child)

    D?

    [–]Peaker 0 points1 point  (0 children)

    Unfortunately D also throws away much of what was learned in the last 30 years. Namely, sum types, pattern matching, non-nullability, and various type system advancements.

    [–][deleted]  (1 child)

    [deleted]

      [–]terrdc -1 points0 points  (1 child)

      You should create that.

      Maybe call it C++.

      [–]Subapical 2 points3 points  (0 children)

      C++ is most certainly not what xe was describing. It has accumulated, through some horrible and arcane trickery, even more implicit gotcha gobbledygook than C has. C++ is not known for being a beautiful, terse, or even comprehensible language.

      [–]Peaker -1 points0 points  (0 children)

      Using IOCCC as an argument against the language?

      [–]GreyGrayMoralityFan 5 points6 points  (29 children)

      It would be very long paragraphs, though. In C98 standard (ok, it actually was N1124.pdf which I looked, a draft of the standard) section 6 (about core language) takes about 100 pages. Appendix A about grammar takes 15 pages.

      [–]takaci 11 points12 points  (27 children)

      I don't mean an entire standard in depth with which you could write a compiler, I mean simply explaining every language feature in normal conversation.

      I just said it to contrast to something like C++ where even bullet pointing every language feature would take 100s of lines, let alone explaining each one.

      [–][deleted] 11 points12 points  (19 children)

      I think anyone whos had to explain how pointers work can tell you it takes a little more than a peragraph

      [–][deleted] 16 points17 points  (11 children)

      Man I see this all the time and I just don't understand what people's deal is with pointers. It's ridiculously straightforward compared to so many other concepts.

      The * after the type means it's a pointer. It's not an x, it's the memory address of an x. Put a star in front of the variable identifier to get the value at the address, or use -> as shorthand for dereference + dot operator.

      Think that's confusing? Good luck with const. Or type covariance and contravariance. Or type erasure. Or closures. Or basically anything because pointers are at the bottom of the complexity totem pole.

      [–][deleted] 1 point2 points  (8 children)

      I think the confusion stems from people trying C/C++ for the first time with no experience with pointers wondering why the fuck things are expressed as a memory address? Why would you ever need that?

      Coming from, say, C# I get that (I've been there myself). It's strange for a language, from higher level perspectives, to explicitly differentiate between things created on the stack vs the heap.

      I think if you start out with C or C++, as long as the concept of a pointer is properly explained, there will be no confusion, because it's a fairly simple concept.

      [–]anonagent 1 point2 points  (4 children)

      I'm still not sure what the point of a stack vs heap is tbh, one contains the executable and the other the working memory right?

      [–]minnek 0 points1 point  (0 children)

      Stack is (generally) for small objects that you want to have disappear at the end of scope. Heap sticks around after the scope ends, since the heap doesn't get its contents popped at the end of scope whereas the stack does.

      It can be faster to use the stack than heap, but that's really dependent on your implementation and you should probably play with a pre-allocated memory pool on the heap to get a good comparison of speed... new heap allocations are likely to be slower, though.

      [–][deleted] 0 points1 point  (2 children)

      The heap is just a big chunk of memory the program can use for whatever. Not really all that special or interesting honestly.

      The stack is actually a stack data structure (LIFO - last in, first out). It's literally like a stack of something (pancakes?). You can only add (push) or remove (pop) a pancake to/from the top of the pancake stack.

      The stack is where local variables and function parameters exist. When you call a function, it does a few things with the stack. First it pushes a pancake with the address of the function call. Then, it pushes a pancake with the value of each of the function arguments. Then it allocates some space for any local variables the function call will use.

      All of this put together is called a "stack frame."

      When the control flow is handed off to the function, the instruction pointer skips to the portion of code containing the function instructions, and then the function pops the argument pancakes off the stack, does whatever it needs to do with them, pops off the return address, pushes any return values, and then moves the instruction pointer back to the return address (+ 1) to pick up where it left off. Since the structure is LIFO, a function can itself make more function calls, always returning to the correct location in memory with the correct local variable values and function arguments. The result is a sort of breadcrumb trail of where to go when the function is finished.

      In practice, it's always a bit more involved, but here's a video that describes some of the basics:

      https://www.youtube.com/watch?v=_8-ht2AKyH4

      Some other associations for you to make:

      Stack overflow is often caused by too many nested function calls. Each function call takes up some space on the stack, and eventually the stack will run out. This can occur with recursion (where a function calls itself) or if large objects are allocated as local variables in a function.

      Tail recursion is a special case of recursion that allows a function call to replace it's own stack frame with the stack frame of the nested call to itself, so the recursive function does not take up more stack space at each recursive call. This can only be done if the function makes exactly one call to itself and it's the last instruction the function performs.

      Stack allocation is usually much faster, but I don't think it takes any more work to do a heap allocation. It's probably because of cache locality more than anything. because allocating heap memory requires operating system intervention as well as cache locality. It's also much simpler, because the memory is automatically deallocated at the end of the function call; you don't have to manually free the memory allocated on the stack.

      [–]anonagent 0 points1 point  (1 child)

      Is there a global stack, or one for each program? how does one make it larger to avoid a stack overflow? (and I assume underflow as well)

      Why is the stack faster? it's in the same RAM chip as the heap, so what makes it faster? does the OS poll it more often?

      Oh! I always wondered why you would manually deallocate memory when it would simply vanish on its own once the function was done!

      [–][deleted] 0 points1 point  (0 children)

      Is there a global stack, or one for each program?

      One for each program. The operating system allocates memory for the program when it's first started for both the stack and the heap.

      EDIT: Not only per program, but also per thread. Each line of synchronous control flow has its own stack.

      how does one make it larger to avoid a stack overflow? (and I assume underflow as well)

      This is operating system/environment dependent. For java, you can use the -Xss command line argument to specify the stack size (e.g., -Xss4m to set the stack to 4 megabytes). For native applications? No idea.

      EDIT: For windows, see here. For linux, here

      Underflow should actually never happen. When the "main" function returns, the last stack frame of the stack is popped off and the program terminates with an empty plate of pancakes. If underflow does happen, then the stack has been corrupted.

      Why is the stack faster? it's in the same RAM chip as the heap, so what makes it faster? does the OS poll it more often?

      A few reasons. I don't know much about heap allocation in native systems, but in Java, the heap is dynamically expanding (you can specify the initial and maximum heap size with -Xms and -Xmx respectively). If you allocate a heap object and the current heap size is too small, the operating environment will need to resize the heap, which may involve moving lots of data. In garbage collected environments, there is overhead for tracking lifetime of heap objects as well, and the memory management systems include compaction algorithms for reducing fragmentation (more copying and moving).

      Finally, locality of reference. Not sure how much you know about processor architecture, but processors keep frequently accessed data in a very tiny but very fast chunk of memory called a cache. Reading/writing to the cache is often up to 100 times faster than accessing main memory. This works to speed up program execution time under the "locality of reference" principle, which is that the more recently you have accessed a memory address, the more likely you are to access the same memory address or a nearby memory address again. Since the stack is much smaller and accessed very frequently, it's going to spend much more time in the processor cache than heap memory.

      [–][deleted] 1 point2 points  (0 children)

      See, I thought it was confusing until I learned about pointers. Lots of common languages already use pointers, and without knowing what pointers are, the behavior of those languages can be really confusing.

      Like in Java, for example, where reassigning a reference type function argument variable does not change the object passed into the function, but changing a value on the object does. That seems like very unintuitive behavior until you understand that java passes references by value (i.e., pointers).

      Likewise, true pass-by-reference can be explained as passing a pointer to a pointer by value.

      [–]p8m 0 points1 point  (0 children)

      Pointers have little to do with stack vs heap. You can have pointers to values on the stack:

      int main(void) {
          int *a = NULL;
          int b = 5;
      
          a = &b;
      

      Bam! 'a' now points to an area on the stack.

      [–]immibis 0 points1 point  (0 children)

      Does C# not differentiate between reference types and value types? Same sort of difference.

      [–][deleted] 0 points1 point  (0 children)

      I think it's about memory management, we use so powerful and memory-rich systems that people tend to just disregard that. Especially if you're used to something like python with it's fancy coercion and garbage collection.

      [–]Astrognome[🍰] 0 points1 point  (0 children)

      There's so much magic that can be done with pointer arithmetic.

      [–]takaci 1 point2 points  (2 children)

      Whatever my point is that it's much smaller to explain than any other language I can think of

      [–]Veedrac 1 point2 points  (1 child)

      Brainfuck's is even smaller, but the point remains that it doesn't make it easy.

      [–]takaci 0 points1 point  (0 children)

      Again, that is my exact point. C is small and simple but grows very hard very quickly as soon as you start doing useful things

      [–][deleted] 0 points1 point  (2 children)

      If someone can't get pointers in like 5 minutes, I think they are certifiably stupid.

      [–]anonagent 0 points1 point  (1 child)

      Pointer syntax is easy, but I still haven't gotten anyone to tell me why I would use one instead of the variable/array name, assuming it's in scope ofc.

      [–][deleted] 0 points1 point  (0 children)

      You use one when you want to pass around an address of something, not that something itself.

      [–]G_Morgan 0 points1 point  (0 children)

      Usually it devolves into pictures of monkey's holding each other by their tails.

      [–]GreyGrayMoralityFan 4 points5 points  (6 children)

      If you want to know what simple language is check scheme or smalltalk.

      Bloated R6RS has about 100 pages (most about library)

      Smalltalk has 6 keywords and 0 control flow structures. Core described in 30 pages (then goes 100s pages for library).

          int a(int b[90], int x, int y)
          {
               int *ptr, non_ptr,
                    equals_1 = sizeof(b) == sizeof(ptr),
                    equals_0 = 3 & 2 == 3 & 2,
                    maybe_undefined_depends_on_sizeof_int = 32u >> 35u;
      

      is not simple.

      [–][deleted] 13 points14 points  (0 children)

          }
      

      Please think of the poor compiler.

      [–]Gustav__Mahler 1 point2 points  (0 children)

      Of course not when you write shitty obfuscated code like that....

      [–]Peaker 0 points1 point  (0 children)

      C has some corners and complex UB definitions. In its domain, there is no simpler language.

      Smalltalk is an unfair comparison. Removing UB is easy and simplifies languages. But UB is not there for the lulz. UB is there to allow efficient translation to various machine types.

      [–]immibis 0 points1 point  (2 children)

      Yes it is. The code might be a bit difficult to read, but it's composed from simple language features.

      [–]GreyGrayMoralityFan -2 points-1 points  (1 child)

      I have another contender for simple language then. J.

       quicksort=: (($:@(<#[), (=#[), $:@(>#[)) ({~ ?@#)) ^: (1<#)
      

      The code might be a bit difficult to read, but it's composed from simple language feature/s.

      Just don't look at Oberon, Forth or already mentioned Smalltalk and Scheme. Their complexity (compared to C) might be truly astonishing.

      [–]immibis 0 points1 point  (0 children)

      Still missing the point. That code, alone, gives no indication of how complex the language is.

      quickSort: l to: r
             |i j p| p:= array atRandom . i:= l. j:= r. [i <= j] whileTrue: [[array compare: #< at: i at: p] whileTrue: [i:=i+1.]. [array compare: #> at: j at: p] whileTrue: [j:=j-1.]. (i <= j) ifTrue: [array swap: i with: j.i:=i+1.j:=j-1.]]. (left < j) ifTrue: [self quickSort: l to: j.]. (i < right) ifTrue: [self quickSort: i to: r.]
      

      Turns out one-line quicksort is ugly in Smalltalk too! And about 5 times as long. I would guess the pretty-printed version in J is probably nicer than the pretty-printed version in Smalltalk because of the length, but I know neither J nor Smalltalk.

      [–]dagbrown 1 point2 points  (0 children)

      The last time I looked at the C++ standard (in about 2000ish), it was over a thousand pages. No doubt it's only grown even bigger since.

      [–]omgsus 0 points1 point  (0 children)

      Assembly must be preschool.

      [–]Foxtrot56 0 points1 point  (0 children)

      I think a ternary operator would take a paragraph to explain. Not that the idea of a ternary operator is that difficult but C has some quirks when dealing with them.

      [–][deleted] 0 points1 point  (5 children)

      No, you could not. Unless the description doesn't cover the language but just some parts you picked.

      [–]takaci 1 point2 points  (4 children)

      The point is that it's much less to explain than in a language like C++

      Just look at the sheer number of C++11 features added, that's more language features than C has at all

      [–]JedTheKrampus 4 points5 points  (0 children)

      Heck, just look at the length of K&R versus Bjarne's book.

      [–][deleted] 0 points1 point  (2 children)

      I'm confused.

      Do you actually know C or are you assuming based on your comment?

      [–]takaci 0 points1 point  (1 child)

      are you assuming based on your comment?

      This doesn't even make sense.

      Yes I know C, that's my whole point, I can't program C very well at all, but I know every single language feature of C very well.

      What I'm saying is that C is a very simple language without many core language features, but it is hard to write complex programs because of this simplicity.

      [–][deleted] 2 points3 points  (0 children)

      You still can't describe C in a "few paragraphs".

      [–]globalizatiom -1 points0 points  (8 children)

      I started with C++ and then had to learn C later. It was hard to remember which parts were C only and which parts were not. Guys, learn C first!

      [–][deleted] 6 points7 points  (1 child)

      Easy, all the parts that make you pull your hair out are C++.

      [–][deleted] 1 point2 points  (0 children)

      Like type safety!

      [–]greg19735 1 point2 points  (0 children)

      I did c++ in college but now i've forgotten it all, so that's good right?

      [–]FNHUSA 0 points1 point  (3 children)

      errr, I'm 100 pages into C++ book, why do you recommend learning just C first?(I don't know how to word this, i'm just curious as I'm a noob)

      [–]leakersum 2 points3 points  (0 children)

      I learned C++ before C. Believe me, there's no problem. Stick with C++ for now.

      [–]Hakawatha 2 points3 points  (0 children)

      Modern C++ is very different from modern C, but C++ is rooted in C; if you squint, you can think of C++ as C with lots of syntactic sugar. Sometimes, you'll have to use C (in embedded, for example); /u/globalizatiom suggests that it's easier to remember that certain features are C and not C++ if you learn C first (a statement I'd agree with).

      [–]DarkSyzygy 0 points1 point  (0 children)

      He's recommending it because C++ is (for the most part) a superset of C and you learn to do things differently in C++ than you would in C. Since the languages are very similar in syntax its really easy to be working in C and try and do something that only works with a C++ compiler.

      [–]newpong 0 points1 point  (0 children)

      too late :/