Live data from Hacker News

Defer available in gcc and clang

gustedt.wordpress.com

191–200 of 262 posts

Re: Defer available in gcc and clang

#191
post #143

Earlier quoted context omitted.

No, but also skip malloc/free until late in the year, and when it comes to heap allocation then don't use example code which allocates and frees single structs, instead introduce concepts like arena allocators to bundle many items with the same max lifetime, pool allocators with generation-counted slots and other memory managements strategies.

Is there any C tutorials you know that do that so I can try to learn how to do it?

Shameless plug ;)

https://floooh.github.io/2018/06/17/handles-vs-pointers.html

This only covers one aspect though (pools indexed by 'generation-counted-index-handles' to solve temporal memory safety - e.g. a runtime solution for use-after-free).

Re: Defer available in gcc and clang

#192
post #182

Earlier quoted context omitted.

The surfaces are all of different size, so the code would have to be more complex, resizing some underlying buffer on demand. You'd have to split up the text rendering into an API to measure the text and an API to render the text, so that you could resize the buffer. So you'd introduce quite a lot of extra complexity. And what would be the benefit? You save up to one malloc and free per string you want to render, but…

Why does the buffer need to be resized? Your malloc version allocates a fixed amount of memory on each iteration. You can allocate the same amount of memory ahead of time. If you were dynamically changing the malloc allocation size on each iteration then you have a case for a growable buffer to do the same, but in that case you would already have all the complexity of which you speak as required to support a dynamica…

The example allocates an SDL_Surface large enough to fit the text string each iteration.

Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop.

But again, what do you gain from that complexity?

Re: Defer available in gcc and clang

#193

Earlier quoted context omitted.

For concurrent processing you'd probably do something like splitting the file names into several batches and process those batches sequentially in each goroutine, so it's very much possible that you'd have an exact same loop for the concurrent scenario. P.S. If you have enough files you don't want to try to open them all at once — Go will start creating more and more threads to handle the "blocked" syscalls (open(2)…

You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why. If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decision…

[deleted]

Re: Defer available in gcc and clang

#194
post #192

Earlier quoted context omitted.

Why does the buffer need to be resized? Your malloc version allocates a fixed amount of memory on each iteration. You can allocate the same amount of memory ahead of time. If you were dynamically changing the malloc allocation size on each iteration then you have a case for a growable buffer to do the same, but in that case you would already have all the complexity of which you speak as required to support a dynamica…

The example allocates an SDL_Surface large enough to fit the text string each iteration. Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop. But again, what do you gain from that complexity?

> The example allocates an SDL_Surface large enough to fit the text string each iteration.

Impossible without knowing how much to allocate, which you indicate would require adding a bunch of complexity. However, I am willing to chalk that up to being a typo. Given that we are now calculating how much to allocate on each iteration, where is the meaningful complexity? I see almost no difference between:

    while (next()) {
        size_t size = measure_text(t);
        void *p = malloc(size);
        draw_text(p, t);
        free(p);
    }
and

    void *p = NULL;
    while (next()) {
        size_t size = measure_text(t);
        void *p = galloc(p, size);
        draw_text(p, t);
    }
    free(p);

Re: Defer available in gcc and clang

#195

Earlier quoted context omitted.

For concurrent processing you'd probably do something like splitting the file names into several batches and process those batches sequentially in each goroutine, so it's very much possible that you'd have an exact same loop for the concurrent scenario. P.S. If you have enough files you don't want to try to open them all at once — Go will start creating more and more threads to handle the "blocked" syscalls (open(2)…

You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why. If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decision…

You have now transformed the easy problem of "iterate through some files" into the much more complex problem of either finding a work queue library or writing your own work queue library; and you're baking in the assumption that the only reasonable way to use that work queue is to make each work item exactly one file.

What you propose is not a bad solution, but don't come here and pretend it's the only reasonable solution for almost all situations. It's not. Sometimes, you want each work item to be a list of files, if processing one file is fast enough for synchronisation overhead to be significant. Often, you don't have to care so much about the wall clock time your loop takes and it's fast enough to just do sequentially. Sometimes, you're implementing a non-important background task where you intentionally want to only bother one core. None of these are super unusual situations.

It is telling that you keep insisting that any solution that's not a one-file-per-work-item work queue is super strange and should be punished by the language's design, when you haven't even responded to my core argument that: sometimes sequential is fast enough.

Re: Defer available in gcc and clang

#196
post #129
post #125

Earlier quoted context omitted.

You're talking about the part of C++ that was inherited from C. Unfortunately, it was way too late to fix by the time RAII was even invented

And the consequence is that, at least in C++, we don't see the benefit you describe of "objects can never be in an uninitialized or partially-initialized state". Anyway, I think this could be fixed, if we wanted to. C just describes the objects as being uninitialized and has a bunch of UB around uninitialized objects. Nothing in C says that an implementation can't make every uninitialized object 0. As such, it would…

It's possible to fix this in application code with a Primitive or NoDefault wrapper that acts like a T, except doesn't have a default constructor. Use Primitive wherever you'd use int that it matters (e.g. struct fields), and leaving it uninitialized will be a compiler error.

Re: Defer available in gcc and clang

#197

Earlier quoted context omitted.

Defer is a restricted form of COMEFROM with automatic labels. You COMEFROM the end of the next `defer` block in the same scope, or from the end of the function (before `return`) if there is no more `defer`. The order of execution of defer-blocks is backwards (bottom-to-top) rather than the typical top-to-bottom. puts("foo"); defer { puts("bar"); } puts("baz"); defer { puts("qux"); } puts("corge"); return; Will evalua…

That is the most cursed description I have seen on how defer works. Ever.

This is how it would look with explicit labels and comefrom:

    puts("foo");
    before_defer0:
    comefrom after_defer1;
    puts("bar");
    after_defer0:
    comefrom before_defer0;
    puts("baz");
    before_defer1:
    comefrom before_ret;
    puts("qux");
    after_defer1:
    comefrom before_defer1;
    puts("corge");
    before_ret:
    comefrom after_defer0;
    return;
---

`defer` is obviously not implemented in this way, it will re-order the code to flow top-to-bottom and have fewer branches, but the control flow is effectively the same thing.

In theory a compiler could implement `comefrom` by re-ordering the basic blocks like `defer` does, so that the actual runtime evaluation of code is still top-to-bottom.

Re: Defer available in gcc and clang

#198
I have a personal aversion to defer as a language feature. Some of this is aesthetic. I prefer code to be linear, which is to say that instructions appear in the order that they are evaluated. Further, the presence of defer almost always implies that there are resources that can leak silently.

I also dislike RAII because it often makes it difficult to reason about when destructors are run and also admits accidental leaks just like defer does. Instead what I would want is essentially a linear type system in the compiler that allows one to annotate data structures that require cleanup and errors if any possible branches fail to execute the cleanup. This has the benefit of making cleanup explicit while also guaranteeing that it happens.

Re: Defer available in gcc and clang

#199
post #12

Earlier quoted context omitted.

I think destructors are different, not better. A destructor can’t automatically handle the case where something doesn’t need to be cleaned up on an early return until something else occurs. Also, destructors are a lot of boilerplate for a one-off cleanup.

> A destructor can’t automatically handle the case where something doesn’t need to be cleaned up on an early return It can. An object with destructor doing clean-up should be created only after such clean-up is needed. In case of a file, for example, a file object should be created at file opening, so that it can close the file in its destructor.

That’s fine when one wants to write a whole class for something. But sometimes a cleanup path is genuinely a one-off, and using something like defer is nice.

Re: Defer available in gcc and clang

#200
post #192

Earlier quoted context omitted.

Why does the buffer need to be resized? Your malloc version allocates a fixed amount of memory on each iteration. You can allocate the same amount of memory ahead of time. If you were dynamically changing the malloc allocation size on each iteration then you have a case for a growable buffer to do the same, but in that case you would already have all the complexity of which you speak as required to support a dynamica…

The example allocates an SDL_Surface large enough to fit the text string each iteration. Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop. But again, what do you gain from that complexity?

I think I've been successfully nerd sniped.

It might be preferable to create a font atlas and just allocate printable ASCII characters as a spritesheet (a single SDL_Texture* reference and an array of rects.) Rather than allocating a texture for each string, you just iterate the string and blit the characters, no new allocations necessary.

If you need something more complex, with kerning and the like, the current version of SDL_TTF can create font atlases for various backends.

Post reply on HN