[HN Gopher] Stupid Smart Pointers in C
___________________________________________________________________
Stupid Smart Pointers in C
Author : seansh
Score : 69 points
Date : 2025-03-17 11:29 UTC (11 hours ago)
(HTM) web link (blog.kevinalbs.com)
(TXT) w3m dump (blog.kevinalbs.com)
| adrianN wrote:
| I wonder how that affects compiler optimization
| dhsysusbsjsi wrote:
| and stack protection cookies
| jcranmer wrote:
| Let's just say there's a reason the author is compiling
| everything with -O0.
| pjdesno wrote:
| Note that this will probably cause branch prediction misses, just
| like thread switching does - modern CPUs have a return address
| predictor which is just a simple stack. I don't think you can
| avoid this without compiler support.
| rwmj wrote:
| Really, don't do this, it's a portability and safety nightmare
| (aside from C not being memory safe already).
|
| C programmers are better off with either of these two techniques:
|
| * Use __attribute__((cleanup)). It's available in GCC and Clang,
| and we hope will be added to the C spec one day. This is widely
| used by open source software, eg. in systemd.
|
| * Use a pool allocator like Samba's talloc
| (https://talloc.samba.org/talloc/doc/html/libtalloc__tutorial...)
| or Apache's APR.
|
| (I didn't include using reference counting, since although that
| is also widely used, I've seen it cause so many bugs, plus it
| interacts badly with how modern CPUs work.)
| MrBuddyCasino wrote:
| > ___attribute__((cleanup))_
|
| Interesting. I'm not very proficient in C, this looks like some
| sort of finalizers for local variables?
| rwmj wrote:
| Correct. You can use it in a simple way to free memory, but
| we've also used it to create scoped locks[1].
|
| This being C, it's not without its problems. You cannot use
| it for values that you want to return from the function (as
| you don't want those to be freed), so any such variables
| cannot be automatically cleaned up on error paths either.
| Also there's no automated checking (it's not Rust!)
|
| Note it's {...} scoped, not function scoped, which makes it
| more useful than Golang's defer.
|
| [1] https://gitlab.com/nbdkit/nbdkit/-/blob/8b36e5a2ea331eed2
| a73...
| fpoling wrote:
| While Go rules effectively prevents usage of defer in
| loops, it is useful occasionally to write:
| if complex_nested_condition { defer cleanup()
| }
| TheDong wrote:
| Even with scope-based defer, you can accomplish
| conditional defers easily enough. In a sane language,
| where conditions are expressions, you could just do:
| defer if complex_nested_condition { cleanup() } else {
| noop() }
|
| In Go, you could do: defer func(run
| bool) { if !run { return } }(condition)
|
| Which admittedly wastes stack space with a noop function
| in the false case, but whatever.
|
| I feel like the number of times I've needed conditional
| defers is almost zero, while the number of times I've had
| to make a new function to ensure scoping is correct is
| huge.
|
| Of especial note, 'mu.Lock(), defer mu.Unlock()' not
| being scope-based is the largest source of deadlocks in
| code. People don't use 'defer' because the scoping rules
| are wrong, code panics before the manual unlock call, and
| then the program is deadlocked forever.
| NekkoDroid wrote:
| > You cannot use it for values that you want to return from
| the function
|
| I would say this is only half true. With some macro magic
| you can actually also return the values :)
|
| https://github.com/systemd/systemd/blob/0201114bb7f347015ed
| 4...
|
| To be fair though, you probably meant without any such
| shenanigans.
| maccard wrote:
| > It's available in GCC and Clang, and we hope will be added to
| the C spec one day. This is widely used by open source
| software, eg. in systemd.
|
| It's odd that the suggestion for a feature lacking in C is to
| use a non standard but well used supported path. c's main
| selling point (IMO) is that it _is_ a standard, and relying on
| compiler vendor extensions kind of defeats the purpose of that.
| rwmj wrote:
| It's so widely used by OS software that you're likely using
| already, that it's unlikely to be removed and much more
| likely to be standardized. This is in fact how
| standardization _ought_ to work - standardize the proven best
| practices.
| maccard wrote:
| I agree. But if we follow that logic then any compiler
| specific feature of either or clang is fair game, even if
| it's not standard. MSVC doesn't support it when compiling
| in C mode, for example.
| lelanthran wrote:
| > But if we follow that logic then any compiler specific
| feature of either or clang is fair game, even if it's not
| standard.
|
| Well, yeah...
|
| How do you think Annex K got in?
| dietr1ch wrote:
| > relying on compiler vendor extensions kind of defeats the
| purpose of that.
|
| Let's be honest, how many compilers are available, and how
| many of those would you actually use?
|
| The answer isn't more than 4 and the 2 compilers you are most
| likely to use among those already support this and probably
| won't stop supporting without a good alternative.
|
| I like standardisation, but you have to be realistic when it
| helps you without a large real cost other than fighting your
| ideals for getting this into the standard first.
| DanielHB wrote:
| There are quite a lot of embedded code that relies on
| obscure C compilers created and maintained by the CPU
| manufacturer.
|
| But then again you are probably not doing a whole lot of
| heap management in embedded code.
| dietr1ch wrote:
| That sounds like the kind of code that you want to be
| done with and never touch again. You can only dream of it
| not being buggy or catching up with the newest standard.
| DanielHB wrote:
| No it is more like the 10000 lines of code running in
| your washing machine, you will probably be updating it in
| the next year revision of the product.
|
| It is quite common for this code to have all variables be
| global and just not have any heap allocations at all.
| Sometimes you don't even have variables in the stack
| either (besides the globals).
| amjoshuamichael wrote:
| For me, the point of writing something in C is portability.
| There were C compilers 30 years ago, there are C compilers
| now, and there will almost certainly be C compilers 30
| years from now. If I want to write a good, portable library
| that's going to be useful for a long time, I'll do it in C.
| This is, at least, the standard in many gamedev circles
| (see: libsdl, libfreetype, the stb_* libraries). Under that
| expectation, I write to a standard, not a compiler.
| rwmj wrote:
| The bad news is that C23 broke a lot of existing code[1].
| We had to do a lot of work in Fedora to fix the resulting
| mess. [1] https://gcc.gnu.org/gcc-15/porting_to.html#c23
| fuhsnn wrote:
| For the cases in linked doc, does adding -std=gnu17 to
| packages not suffice?
|
| I would consider the union initializer change (require
| adding -fzero-init-padding-bits=unions for old behavior)
| much more hidden and dangerous, which is not directly
| related to ISO C23 standard.
| rwmj wrote:
| It's true that it does, yes. However that would still
| require changes to the build system. In any case for the
| vast majority of the packages we decided to fix _(if you
| think this is a fix!)_ the code.
| fuhsnn wrote:
| >if you think this is a fix
|
| I would count it as doing maintenance work for the
| upstream, kudos for doing this!
| dietr1ch wrote:
| If I touch C it has to have control over allocations,
| memory layout, and wrapping low level code into functions
| I can call from other languages.
|
| I'd target the latest C standard and won't even care to
| know how many old, niche compilers I'm leaving out. These
| are vastly different uses for C and obviously your gaols
| drastically change your standard or compiler targeted.
| flohofwoe wrote:
| For accessing any post-1970s operating system feature
| (e.g. async IO or virtual memory) you already cannot use
| standard C anymore (and POSIX is not the C stdlib).
|
| The libraries you listed are all full of platform-
| specific code, and also have plenty of compiler-specific
| code behind ifdefs (for instance the stb headers have
| MSVC specific declspec declarations in them).
|
| E.g. there is hardly any real-world C code out there that
| is 'pure standard C', if the code compiles on different
| compilers and for different target platforms then that's
| because the code specifically supports those compilers
| and target platforms.
| amjoshuamichael wrote:
| My argument is that using these non-standard extensions
| to do important things like memory management in a C
| library is malpractice--it effectively locks down the
| library to specific C compilers. I'm sure that's fine if
| you're writing to clang specifically, but at that point,
| you can just write C++. libfreetype & stb_* are used and
| continue to be used _because_ they can be relied on to be
| portable, and using compiler-specific extensions (without
| ifdefs) defeats that. If I relied on a clang-specific
| `defer`, I 'm preventing my library from possibly being
| compiled via a future C compiler, let alone the compilers
| that exist now. To me, that's the point of writing C
| instead of C++ for a library (unless you're just a fan of
| the simplicity, which is more of an ideological, opinion-
| based reason).
| maccard wrote:
| If this is the argument then the actual standardisation is
| useless. I primarily use windows so I'm affected by one of
| the major compilers that doesn't support this feature. This
| is no different to saying "chrome supports feature X, and
| realistically has y% market share so don't let the fact
| that other browsers exist get in the way".
|
| Call it a posix extension, fair enough. But if your reason
| for writing C is that it's portable, don't go relying on
| non portable vendor specific extensions.
| dietr1ch wrote:
| It's not, and even if there's something in the standard,
| your compiler of choice might not support it yet.
|
| It's the same thing with the web and browser vendors,
| there's a constant mismatch, browsers propose and
| implement things and they may get standardized, and the
| standard dictates new requirements which might get
| implemented by all vendors.
|
| The point of standardisation is defining behaviour for
| the things that are implemented as exploratory
| improvements and should be implemented on the more
| conservative compilers.
|
| It's your choice whether to target the standard or a few
| selected compilers, there's a cost for both options
| between being late to improvements vs the possibility of
| needing to revisit your code around each of the
| "extensions" you decided to depend on.
|
| If in certain projects portability is somehow of upmost
| importance, then any discussion around looking through
| the standard's black box to reach out for new stuff is
| kind of useless.
| flohofwoe wrote:
| Standard C is only the least common denominator that compiler
| vendors agreed on, and the C standard committee works
| 'reactively' by mostly standardizing features that have been
| in common use as non-standard extensions - sometimes for
| decades before standardization happens (this is probably the
| main difference to the C++ committee).
|
| The *actual* power and flexibility of C lies in the non-
| standard, vendor-specific language extensions.
| shakna wrote:
| Defer was almost part of the C23 standard, but didn't quite
| make it. They've slimmed and changed a few things, and it
| looks like it very much might be part of the next.
|
| [0] https://thephd.dev/c2y-the-defer-technical-specification-
| its...
|
| [1] https://thephd.dev/_vendor/future_cxx/technical%20specifi
| cat...
| masklinn wrote:
| > we hope will be added to the C spec one day
|
| defer seems to be making significant progress (having a
| passionate and motivated advocate in Meneide, and a full TS)
| rwmj wrote:
| Last time I looked this was (golang-like) function scoped,
| not { } scoped, which means it's a bad idea. My feedback was
| the committee should simply standardize the existing
| attribute / behaviour, as that is widely used already.
|
| (EDIT: I'm wrong, see reply)
| masklinn wrote:
| > Last time I looked this was (golang-like) function
| scoped, not { } scoped, which means it's a bad idea.
|
| Might have been the previous attempt from years ago,
| because being block scoped (unlike go) literally has its
| own section in https://thephd.dev/c2y-the-defer-technical-
| specification-its...
| EPWN3D wrote:
| defer is nice, but I really want the cleanup attribute since
| it could in theory by applied to the return type of a
| function. In other words you could have malloc return a
| pointer with the cleanup attribute that automatically frees
| it at end of scope if it's non-NULL. (And if you want to
| persist the pointer just assign to a different variable and
| zero out the one malloc gave you.)
| masklinn wrote:
| > In other words you could have malloc return a pointer
| with the cleanup attribute that automatically frees it at
| end of scope if it's non-NULL.
|
| That is not, as far as I know, how __attribute__((cleanup))
| works. It just invokes the callback when the value goes out
| of scope. So you can't have malloc return an implicitly
| cleanup'd pointer unless malloc is a macro, in which case
| you can do the same with a defer block.
| legohead wrote:
| My C programs never consumed gigs of memory. So I (like many
| others I assume) made a memory manager and never freed
| anything. You'd ask it for memory and it kept a list of various
| sizes it allocated and returned what you needed to be re-used.
| Freeing and allocating is slow, and error prone, so just avoid
| it!
| KerrAvon wrote:
| A venerable and completely reasonable approach for resource-
| constrained environments and/or programs with very small
| memory requirements (kilobytes).
| adamrezich wrote:
| What makes you think that this approach is only useful for
| resource-constrained circumstances?
| rwmj wrote:
| Yeah it's reasonable. Unfortunately if you do it, and you
| run tools like Coverity, it'll produces reams of
| complaints about how you're leaking memory :-( There was
| one project which was genuinely a short-lived program
| that never needed to free memory, but in the end I gave
| in and added free() statements everywhere. Otherwise we
| could never have got it into RHEL.
| adamrezich wrote:
| Why not just put all the free()s at the end of main()
| behind an #ifdef DEBUG or something?
| legohead wrote:
| What are your issues with the memory requirements being
| small? One of the programs was a MUD that consumed a couple
| hundred megabytes, and I never had issues with it.
|
| I mentioned gigabytes because of how mine specifically
| worked. It allocated chunks in powers of 2, so there was
| some % of memory that wasn't being used. For instance, If
| you only need 20 bytes for a string, you got back a pointer
| for a chunk of 32 bytes. Being just a game, and side
| project, I never gave it much thought, so I'm curious to
| hear your input.
| nine_k wrote:
| Well, it's basically an implementation of a memory allocator.
|
| But how did you determine what you could re-use? That's the
| hard problem, one that's equivalent to calling free() at the
| right time.
| pajko wrote:
| There's a complete implementation available at
| https://github.com/Snaipe/libcsptr
| bsenftner wrote:
| Pointers and memory management is not hard. Stop this childish
| lie.
|
| You allocate memory, and you remember to give it back. Failing to
| do that is not "difficult", it's lazy, it's not maintaining what
| you are doing.
|
| This entire "memory safety" nonsense is market propaganda to
| cripple younger, inexperienced programmers, selling them
| complexity nonsense when all you need to do is remember you
| allocated, and deallocate when you are done.
|
| Seriously, don't play the role of an idiot. That's what this
| entire line of reasoning is: idiot talk. Just remember to
| deallocate, just design systems that manage their memory
| correctly, do not "wing it", design and follow that design.
|
| It is seriously easy when you do not wing it.
| bsenftner wrote:
| I really consider the down votes to be people that want to wing
| it, not be serious developers, and follow the parrot horde that
| marketing creates. This is obvious if you really understand
| what you are doing as a developer, which you should.
| UncleMeat wrote:
| I downvoted because in my mind _you_ are winging it. "Just
| give it back" works well for simple cases, I suppose.
|
| We observe that engineering teams struggle to write correct
| code without tools helping them. This is just an unavoidable
| fact. _Even with_ tools that are unsound we still see oodles
| of memory safety bugs. This is true for small projects run by
| individuals up to massive projects with hundreds or thousands
| of developers. There are few activities as humbling as taking
| a project and throwing the sanitizers at it.
|
| And bugs aren't "well you called malloc at the top of the
| function and forgot to call free at the bottom." Real systems
| have lifetime management that is vastly more complex than
| this and it is just not the case that telling people to not
| suck mitigates bugs.
| bsenftner wrote:
| I'm advocating to design, and then follow the design, and
| when the design is found lacking redesign to include the
| new understanding. This writing of software career is all
| about understanding, and automating that understanding. Due
| to market pressures, many companies try to make due with
| developers that take shortcuts, these shortcut takers the
| majority of developers today, skewing the intellectual
| foundations of the entire industry. Taking shortcuts does
| not negate the fact that taking a shortcut is short
| sheeting one's understanding of what is actually occurring
| in that situation. These shortcuts are lazy non-
| understandings, and that harms the project, it's
| architecture, and increases the cognitive load on
| maintenance. It's creating problems for others and bailing,
| hoping you're not trapped maintaining the complex mess.
| UncleMeat wrote:
| And I'm telling you that designing an application with a
| coherent memory management plan _still_ leads to teams
| producing errors and bugs that are effectively prevented
| with sound tools. Soundness is not a shortcut.
| AnimalMuppet wrote:
| You can consider whatever you want. That doesn't make it
| accurate. The reason _I_ downvoted was for unnecessary
| inflammatory language. Your point would have been better
| without it (more likely to be heard by the people you claim
| to be talking to, at a minimum).
|
| If you're actually trying to talk to people, if you're not
| just here to say "I'm smart and you're stupid" to gratify
| your ego, then why talk in a way that makes other people less
| likely to listen?
| bsenftner wrote:
| You are correct, and I seriously need to work on my
| language usage.
| AnimalMuppet wrote:
| Well, see, _I_ never give in to the temptation of using
| inflammatory language. Never... um, never _today_... um,
| so far... I think...
|
| We've all been there. (Well, maybe dang hasn't. Most of
| the rest of us have, though.)
| jraph wrote:
| I guess you are "the one". This means you won't fail this stuff
| and this discussion is not for you, it is for the rest of us
| who would.
|
| https://rachelbythebay.com/w/2018/04/28/meta/
| bsenftner wrote:
| I'm advocating not to wing it, design up front and then
| follow that design. When the design is found lacking,
| redesign with the entire system in mind. Basically, all I'm
| saying is do not take short cuts, they are not short cuts.
| Your project may finish faster, but you are harming yourself
| as a developer.
| jraph wrote:
| I had initially written a very snarky comment here, but
| this one [1] actually expresses my view quite well in a
| respectful way, I would answer with this. I guess the
| discussion can continue there as well.
|
| [1] https://news.ycombinator.com/item?id=43387334#43388305
| bcrl wrote:
| I agree with the grandparent mostly because the article
| doesn't have any real world applications.
|
| Forgetting to free memory that is allocated and then used
| inside of a function is the rarest kind of memory management
| bug that I have run into in large code bases. It's frequently
| obvious if you read the function and are following good
| practices by making the code clean and easy to read / follow.
|
| The ones that bite are typically a pointer embedded in some
| object in the middle of a complicated data structure that
| persists after a function returns. Reference counting may or
| may not be involved. It may be a cache of some sort to reduce
| CPU overhead from recomputing some expensive operation. It's
| rarely a chunk of memory that has just been allocated. To
| actually recover the lost memory in those cases is going to
| need something more complicated like garbage collection.
|
| But garbage collection is really hard to retrofit into C when
| libraries are involved as who knows what kind of pointer
| manipulation madness exists inside other people's code.
|
| What would be really interesting is if someone made a C
| compiler that replaced pointers with fat pointers that could
| be used to track references and ensure they are valid before
| dereferencing. Sure, it would be an ABI bump equivalent to
| implementing a new architecture along with plenty of fixups
| in legacy C code, but we've done that before. The security
| pendulum has swung over to the point that rebuilding the
| world would be considered worthwhile as compared to where we
| stood 10-15 years ago. It'd certainly be a lot of work to get
| that working compared to a simple hack per the Fine Article,
| but it would have real value.
| jraph wrote:
| Yep, okay, I mostly agree with you on this.
| empath75 wrote:
| Why do you use pointers and a high level language like C when
| you could just write assembly and load and unload all your
| instructions and data into registers directly. Why do you need
| functions? You could just use nothing but JMP instructions.
| There's a whole lot of stuff that C handles for you that is
| completely unnecessary if you really understood assembly and
| paid attention to what you're doing.
| bsenftner wrote:
| I'm from the era that when I was taught Assembly, half way
| through the class we'd written vi (the editor), and when
| finishing that one semester we had a working C compiler. When
| I write C, I drop into Assembly often, and tend to consider C
| a macro language over Assembly. It's not, but when you really
| understand, it is.
| empath75 wrote:
| And Rust developers drop down and manage memory directly
| when they need to and even inline assembly, sometimes.
| nottorp wrote:
| Yes we know, all the world's problems can be solved with
| a rewrite in Rust.
|
| Andre Malraux was right: "The 21st century will be
| religious or it will not be".
|
| He just got the definition of religion wrong.
| rout39574 wrote:
| Performance art?
| ranger207 wrote:
| Yes, and the general trend of falling traffic fatalities is
| because people are driving better, right? Nobody's perfect,
| most people are far from perfect, and if it's possible to
| automate things that let you do better, we should do that
| bsenftner wrote:
| Beware of automation that negates understanding. At some
| point, changes or maintenance requirements will need to
| revisit the situation. If it is wrapped in some time
| consuming complexity, it will just be thrown out.
| jcranmer wrote:
| We have 50 years of experience of code telling us that, no,
| programmers are not consistently capable of avoiding memory
| safety just by being good about it. Saying that it's just a
| failing of lesser programmers is the height of extreme
| arrogance, since I guarantee you that you've written memory
| safety vulnerabilities if you've written any significant amount
| of C code.
|
| The problem is not that the rules are hard to follow--they're
| actually quite easy! The problem is that, to follow the rules,
| you need to set up certain invariants, and in any appreciably
| large codebase, _remembering_ all of the invariants is
| challenging. As an example, here 's a recent memory safety
| vulnerability I accidentally created: int
| map_value(map_t *map, void *key) { // This returns a
| pointer to the internals of map, so it's invalidated //
| any time map is changed. Therefore, don't change the map while
| this // value is live. int *value =
| add_to_map(map, key, default_value()); // ... 300
| lines of code later... // oops, need to recurse, this
| invalidated value... int inner = map_value(value,
| f(key)); // ... 300 lines of code later...
| // Hi, this is now a use-after-free! *value = 5;
| return value; }
|
| It's not that I'm too stupid to figure out how to avoid use-
| after-frees, it's that in the course of refactoring, I broke an
| invariant I forgot I needed. And the advantage of a language
| like Rust is that it bops me on the head when I do this.
| bsenftner wrote:
| We have more than 50 years of experience in this, I've been
| coding for 50 years myself, and there was a huge industry
| when I started.
|
| I still follow KISS, and if you're writing 600+ line
| functions, it is little surprise you forget things in the
| tightly written logic of code.
| alextingle wrote:
| Is your function body really 600+ LOC?? If so, then I think I
| might have found your problem...
| icedchai wrote:
| I one worked on a commercial product, a mix of C++ calling
| our own C libraries, that had 4000+ LOC in a single case
| statement. This was one of my first jobs out of school so I
| was shocked.
| minaguib wrote:
| As someone who has been designing, writing, and operating high-
| performance systems for decades, I can guarantee you that it
| does not boil down to "laziness".
|
| Everyone starts with the best of intentions. malloc() and
| free() pairs. Then inevitable complexity comes in - the
| function gets split to multiple, then across modules, and maybe
| even across systems/services (for other shareable resources).
|
| The mental overhead of ensuring the releases grows. It _is_
| hard, and that's most definitely not a lie beyond any trivial
| implementation.
|
| Surprisingly "just design systems that manage their memory
| correctly", as you said, is a very legitimate solution. It just
| so happens that those systems need good language support, to
| offload a good chunk of the complexity from the programmer's
| brain to the machine.
| bsenftner wrote:
| No, it is laziness, at the system architecture level. I've
| been doing this for decades too, in major corporations,
| writing the big name services that millions to billions of
| people use. The system architects are lazy, they do not want
| to do the accounting - that is all it is, just accounting of
| the resources one has and their current states, integrating
| that accounting tracking system into the environment - but
| few to none do, because it creates hard accountability, which
| they do not want. A soup of complexity is better for them, it
| grows their staff.
|
| I've been playing this game long enough to see the fatal
| flaws built in, which grows complexity, staff, and the
| magnitude of the failures.
| qwertox wrote:
| You can have situations like
|
| https://github.com/bsenftner/kvs/blob/master/kvs/kvs.cpp#L72...
|
| where it's then not clear why there is no call to
|
| `kv.m_binarySize = byte_size`
|
| after calling
|
| `kv.mp_binaryData = (uint8_t _)malloc( sizeof(uint8_t)_
| byte_size );`
|
| because it seems like `kv.mp_binaryData` will now have a
| different size than it had before. That is, there will be a
| mismatch. Though it should not affect the `free` call.
|
| I hope I'm missing something because I just dealt with the code
| for around 5 minutes.
| bsenftner wrote:
| That m_binarySize should have been removed, nothing
| referenced it beyond it's own code. I knew it did not affect
| the free() call and left it. That entire KVS lib is a fine
| example of KISS, it's so small I can hold it in my head, and
| issues like that m_binarySize field are just left because
| they end up being nops.
| scoopr wrote:
| Oh well, maybe we'll soon have `defer`? [0]
|
| [0] https://thephd.dev/c2y-the-defer-technical-specification-
| its...
| usrnm wrote:
| That's sad. Having migrated from C++ to golang a few years ago,
| I find defer vastly inferior to C++ destructors. Rust did it
| right with its drop trait, I think it's a much better approach
| fuhsnn wrote:
| The proposed C defer is scope-based unlike Go. So in the
| spirit of OP article, you can basically hand roll not only
| C++ destructor as defer {obj.dtor()} but also Rust Drop as
| defer {obj.notmoved() ? Drop()}
| DanielHB wrote:
| What do you mean defer isn't scope based in Go?
|
| (not super experienced Go developer)
| masklinn wrote:
| In Go, defers are function scoped not block scoped.
| TheDong wrote:
| I mean, you just write all your scopes as `(func() {
| })()` in go, and it works out fine.
|
| Adding `func() {}()` scopes won't break existing code
| usually, though if you use 'break' or 'continue' you
| might have to make some changes to make it compile, like
| so:
|
| https://go.dev/play/p/_Gq4QYtyMmp
|
| see, no other issues, works exactly like you'd expect
| DanielHB wrote:
| Right, that makes more sense.
| devnull3 wrote:
| mentioned in the article at [1]
|
| [1] https://thephd.dev/c2y-the-defer-technical-
| specification-its...
| masklinn wrote:
| Although that is true, the author has expounded at lengths on
| the unsuitability of RAII _to the C programming langage_ ,
| and as a big fan of RAII the explanations were convincing.
| Dwedit wrote:
| Even Zig which is extremely against "Hidden control flow"
| (so no operator overloading, etc.) added in the "defer"
| feature.
| vbezhenar wrote:
| What's "drop trait" for C? There are no any traits in C.
| abcd_f wrote:
| Hacky and not really fit for production for more reasons than
| one, but clever and nice nonetheless. Good stuff.
| feverzsj wrote:
| Just C programmer's daily struggle to mimic a fraction of C++.
| Joker_vD wrote:
| Wow, an actual, purposeful, and quite general return-pointer-
| smashing gadget, built right into the program itself. Just what
| any program written in C needs.
| Tewboo wrote:
| Smart pointers in C often feel like trying to force a square peg
| into a round hole. They're powerful, but without native language
| support like C++, they can lead to more complexity than they
| solve.
| torlok wrote:
| I've heard enough "C is superior to C++" arguments from game
| developers who then go and use header structs for inheritance,
| or X macros, enums, and switch statements for virtual
| functions, to know that more complexity isn't an issue as long
| as people feel clever and validated.
| qalmakka wrote:
| Or rather, given that every relevant C compiler is also a c++
| compiler, just compile as c++ and use std::unique_ptr? I love C
| but I just can't understand the mental gymnastics of people that
| prefer this kind of hacks compared to just using C++
| kllrnohj wrote:
| There's a lot of either "I like to pretend C is simple and
| simple is good" or "C++ has things I don't like, so I will
| refuse to also use the things I do like out of spite". You see
| it all over the place here whenever C or C++ comes up.
| shakna wrote:
| Unfortunately, that's not true.
|
| C is not 100% compatible with C++.
|
| There's a whole heap of incompatibilities that you can hit,
| that will prevent a lot of non-trivial C programs from
| compiling under C++. Things like character literals being a
| char in C++ and an int in C. Or C allowing designated
| initialisers for arrays, but C++ not.
| afarah1 wrote:
| See also the "arena allocator", which has been discussed here
| before: https://nullprogram.com/blog/2023/09/27/
|
| I haven't used it personally yet, but it addresses the same issue
| with a different approach, also related to stack-like lifetimes.
|
| I've used simple reference counting before, also somewhat
| relevant in this context, and which skeeto also has a nice post
| about: https://nullprogram.com/blog/2015/02/17/
| p0w3n3d wrote:
| I see undefined behaviours. they walk they talk
| they [0?W0OF??0?r??reeBSD they don't know they're
| undefined.
| queuebert wrote:
| C is the LS engine of programming languages. People love to drop
| it in and mod it until it blows up.
| whatsakandr wrote:
| This article should of had the conclusion of this is why you
| should use arena allocator.
| Dwedit wrote:
| Highjacking the return address can only be done if you know you
| actually have a return address, and a reliable way to get to that
| return address. Function inlining can change that, adding local
| variables could change that, omitting frame pointer, etc.
|
| It would also need to be a function that will truly be
| implemented as one following the ABI, which usually happens when
| the function is exported. Often times, internal functions won't
| follow the platform ABI exactly.
|
| Just changing the compiler version is probably enough to break
| anything like this.
|
| Save the return address highjacking stuff for assembly code.
|
| ---
|
| Meanwhile, I personally have written C code that does mess with
| the stack pointer. It's GBA homebrew, so the program won't quit
| or finish execution, and resetting the stack pointer has the
| effect of giving you a little more stack memory.
| flohofwoe wrote:
| IMHO trying to emulate smart pointers in C is fixing a problem
| that shouldn't exist in the first place, and is also a problem in
| C++ code that uses smart pointers for memory management of
| individual objects.
|
| Objects often come in batches of the same type and similar
| maximum lifetime, so let's make use of that.
|
| Instead of tracking the individual lifetimes of thousands of
| objects it is often possible to group thousands of objects into
| just a handful of lifetime buckets.
|
| Then use one arena allocator per lifetime bucket, and at the end
| of the 'bucket lifetime' discard the entire arena with all items
| in it (which of course assumes that there are no destructors to
| be called).
|
| And suddenly you reduced a tricky problem (manually keeping track
| of thousands of lifetimes) to a trivial problem (manually keeping
| track of only a handful lifetimes).
|
| And for the doubters: Zig demonstrates quite nicely that this
| approach works well also for big code bases, at least when the
| stdlib is built around that idea.
| qwertox wrote:
| Not to mention that any future CPU microcode update released in
| order to mitigate some serious CVE might break the entire product
| you've been shipping, just because it relied on some stack
| manipulation wizardry.
| PeterWhittaker wrote:
| 1) 2018
|
| 2) _I recently discovered the implementation of free_on_exit won
| 't work if called directly from main if gcc aligns the stack. In
| this case, main adds padding between the saved eip and the saved
| ebp, (example). I think this can be fixed some tweaking, and will
| update this article when it is fixed._
|
| I do not believe the article was updated, suggesting that the
| "tweaking" was far more complex than the author expected...
|
| ...which doesn't surprise me, because the overall tone is one of
| a clever but far-less-experienced-than-they-think programmer
| having what they think is a flash of insight and realizing
| thereby they can solve simply a problem that has plagued the
| industry and community for decades.
| mac3n wrote:
| this is way overkill
|
| the way i do this in C looks like initialize
| all resource pointers to NULL; attempt all
| allocations; if all pointers are non-NULL, do the
| thing (typically calling another routine) free all
| non-NULL pointers
|
| realloc(ptr, 0) nicely handles allocations and possible-NULL
| deallocations
| mac3n wrote:
| if you must have a `free_on_exit()` (for example, if you
| allocate a variable number of pointers in a loop) then build
| your own defer stack registering pointers using memory that you
| allocate
| zabzonk wrote:
| might as well free the NULL pointers as well - this is totally
| valid C and can simplify the code
| anacrolix wrote:
| The naive assumption is that shared_ptr is always better than
| manual tracking. It's not. Tracking and cleaning up resources
| individually is a burden at scale.
| casenmgreen wrote:
| I maintain a combined error and resource state per thread.
|
| It is first argument to all functions.
|
| If no errors, function proceeds - if error, function instead
| simply immediately returns.
|
| When allocating resource, resource is recorded in a btree in the
| state.
|
| When in a function an error occurs, error is recorded in state;
| after this point _no code executes_ , because all code runs only
| if no errors.
|
| At end of function is boilerplate error, which is added to error
| state if an error has occurred. So for example if we try to open
| file and out of disk, we first get error "fopen failed no disk",
| then second error "opening file failed", and then all parent
| functions in the current call stack will submit _their_ errors,
| and you get a call stack.
|
| Program then proceeds to exit(), and immediately before exit
| frees all resources (and in correct order) as recorded in btree,
| and prints error stack.
___________________________________________________________________
(page generated 2025-03-17 23:01 UTC)