Programming thread

There's nothing stopping you from rolling your own "RAII malloc" though, you can set up an arena allocator (or equivalent) in your local scope, and trash it on scope exit.
Windows will actually let you easily create your own private heap that works the same way as "the" CRT malloc. Not sure about Linux.
Perfect, trashing the heap on scope exit is what I'm looking for.
I can't really make sense of your smart pointer scheme, but afaict it has no way to detect when lifetimes end (so it can't replace refcounting) and it also sounds like it'd have overhead on every access, which is a lot. For comparison, refcounting only has overhead on assignment and tracing GCs typically have small overhead during assignment and large infrequent overhead on allocation.

Sorry if I'm retreading basics here, but just from your post it's not really clear what level you're at.
No worries, appreciate your insight as well. I think I worded the question pretty poorly, since it was less language specific to C and more a question of languages/compilers in general. Alloca() - and its associated limitations - is kind of the naive implementation of what I'm thinking about, memory that expires when its stack frame ends.

I'm thinking about ownership and automatic memory management without garbage collection, and what the potential pitfalls might be. To be honest, I think I'm in over my head. The answer seems both obvious (the memory is no longer needed when the system closes), and obscure (how to determine a system is closed).

While doing some looking into this I found a whitepaper for something called Perceus and a language called Koka; Perceus appears to be ref-counting without garbage collection, which is what I'm interested in, so my initial feeling is that I'm going to have to do some more reading up before going anywhere with this idea.
 
Perfect, trashing the heap on scope exit is what I'm looking for.
It's common to use scratch space for this purpose, just have a big array that matches the default stack size (or more if needed), when you want to call something that needs dynamic memory you give it an allocator created on the stack that wraps the scratch buffer, the allocator will track where the ptr is inside the buffer to allocate the next "malloc" request, the allocator and whatever uses it are within the same block so they share a common lifetime. You get all the benefits of RAII without ghost allocations and with less mental overhead. It may seem more complicated but when you use RAII you will have to pay the piper eventually, e.g. 50% of all allocations in Chrome are std::string allocations, typing a character in the address bar does 10,000 mallocs (they are likely all ghost allocations from using std::string naively).
 
I intuitively never trusted AIslop code and it looks like I was right
we_wuz_coders.png
In other news, I'm a rocket scientist since I own a cheap solar calculator.

Does this have strong "WE WUZ KANGZ" vibes to anyone else?

Edit: I was pleasantly surprised by the quality of this recommended video
 
Last edited:
View attachment 6783637
In other news, I'm a rocket scientist since I own a cheap solar calculator.

Does this have strong "WE WUZ KANGZ" vibes to anyone else?

Edit: I was pleasantly surprised by the quality of this recommended video
The video and the state of AI tools in general remind me of the old episode of Wishbone about H.G. Wells' The Time Machine which wove together the message of the novel with the need for numeracy and not to rely on calculators to excess
Wishbone was of course mostly about classic literature but they sure nailed their one STEM episode
 
Here's a working version for GAS:
Code:
.intel_syntax noprefix
.data
str:
.asciz "Hello world!\n"
str_end:
str_length:
.quad str_end - str

.text
.global main
main:
mov rdi, 1
lea rsi, [rip+str]
mov rdx, QWORD PTR [rip+str_length]
mov rax, 1
syscall

xor rax, rax
ret
Finally back home and tested your code, it does the same thing. Also, you have a seg fault.
1735050213796.png
Like I said, I don't think this is a code issue, it's a gdb issue. You can run mine if you want.

(nasm)
Code:
SECTION .data
    msg: db "Hello, world!", 10
    msg_len: equ $-msg
    
SECTION .text
global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, msg_len
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall
 
Finally back home and tested your code, it does the same thing. Also, you have a seg fault.
That's weird, both versions work fine for me. I'm on GDB 15.2, maybe check you have the latest version?

That's not a segfault, GDB makes it very clear when that's the case.
 
That's weird, both versions work fine for me. I'm on GDB 15.2, maybe check you have the latest version?

That's not a segfault, GDB makes it very clear when that's the case.
I'm not familiar with GAS but from my brief searching you can't end a program with ret. Changing last two lines to mov rax, 60 and syscall fixes the seg fault. Are you running the assembly by calling it from C or something?
(And before you ask, no, changing str_end - str to str_length - str isn't what's causing the fault, they both equal 14 - which is strange to me because it should be 13 - unless it's actually being null-terminated under the hood which is also strange)
1735053120368.png
 
Last edited:
  • Informative
Reactions: Belisarius Cawl
Are you running the assembly by calling it from C or something?
Ah, I see what you're doing. I was doing...

Code:
$ as tmp.s -o tmp.o
$ gcc tmp.o -o tmp

...and gcc calls ld with the C library, which is why it starts with main rather than _start.

If you're writing a normal userspace program, this is nearly always what you want to do. The code that runs before main takes care of boring stuff like setting up the stack, initializing libraries etc. It also calls exit() after main returns.

And before you ask, no, changing str_end - str to str_length - str isn't what's causing the fault, they both equal 14 - which is strange to me because it should be 13 - unless it's actually being null-terminated under the hood which is also strange
Yeah, this is a slight bug with my code, it's also writing out the null terminator. In my defence I was drunk when I wrote it.
 
I'm not familiar with GAS but from my brief searching you can't end a program with ret. Changing last two lines to mov rax, 60 and syscall fixes the seg fault. Are you running the assembly by calling it from C or something?
(And before you ask, no, changing str_end - str to str_length - str isn't what's causing the fault, they both equal 14 - which is strange to me because it should be 13 - unless it's actually being null-terminated under the hood which is also strange)
View attachment 6784975
Wow, syntax highlighting. How'd you pick the color?
 
Oh lol that's just manual highlighting with the color palette on the top of the message box. You can change the font in the 3 dots menu to the right of it and clicking the font family drop-down menu.
I know but I meant was it from an IDE or editor colorscheme that you have? (I use jellybeans in Vim and cyberdream.nvim in Neovim.)
 
A question for the pros here, if you want to answer.

When you're dealing with recursive functions, do you imagine the whole tree of functions, or closely keep track on which step you are mentally? Or do you just summarize what is supposed to do and call it a day?

Like for:
Python:
def f(n):
  if n <= 1:
    return n
  return f(n - 1) + f(n - 2)

print(f(8))

This one is simple, but would you imagine or picture in your head the whole tree/paths?
 
Can someone link an explanation or provide a summary of Rust drama with the Linux kernel or Rust drama in general and why trannies apparently latch onto it and why it needs to be in everything.

A question for the pros here, if you want to answer.

When you're dealing with recursive functions, do you imagine the whole tree of functions, or closely keep track on which step you are mentally? Or do you just summarize what is supposed to do and call it a day?

Like for:
Python:
def f(n):
  if n <= 1:
    return n
  return f(n - 1) + f(n - 2)

print(f(8))

This one is simple, but would you imagine or picture in your head the whole tree/paths?
I'm not great at recursion but I "step through" in a serial manner by jumping to the step it's at and unwinding to process what's going on. It gets too complicated, too fast, for me to build a mental tree after a few steps, a lot easier to start on a step and go back to the "root" of the tree. That said, this has never happened in the real world for me, recursion is avoided and a lot of people simply never do it.
 
A question for the pros here, if you want to answer.

When you're dealing with recursive functions, do you imagine the whole tree of functions, or closely keep track on which step you are mentally? Or do you just summarize what is supposed to do and call it a day?

Like for:
Python:
def f(n):
  if n <= 1:
    return n
  return f(n - 1) + f(n - 2)

print(f(8))

This one is simple, but would you imagine or picture in your head the whole tree/paths?
It's often convenient that you have the option to just think of it in terms of "what is this function doing on this immediate little subset of the problem".

So if you're processing a tree structure recursively, most of the time you spend coding, you can spend ignoring the big picture and just think about the specific little node you're working on in the immediate.

But in the back of your mind, you do need to remember that all recursive functions must have some base case where they finish their work, don't recurse, and return to trace the results back up the call stack.

The base case is usually easy enough and I can tack it to the front of function and the more elaborate meat of the logic is towards the end.

Here's a specific example from my little side project I've been working on for months.

The project involved a daemon that ran tasks that operate on streaming media sinks and sources. The daemon took as input a config file that specified those tasks, sinks and sources. But some sinks+sources can have multiple channels, so you can rewrite your config file to specify multiple tasks (that run in parallel).

But this was a simple enough scenario that I wondered, when you've got one task with a two-channel sink and a two-channel source, why can't the daemon detect and parallelize those itself? Maybe it could have some sort of command line option that would expand a naive config file into a more efficient one?

So I wanted to write a function that optimized a config. This is naturally a good fit for recursion, I think, because it involves tracing down a parse tree and possibly returning modified nodes.

The optimize function roughly would look like:
Code:
let rec optimize input =
  let optimize_step input =
    failwith "todo finish optimize step"
  in

  let output = optimize_step input in
  if output = input
  then output
  else optimize output

So the internal function, optimize_step, only expands one set of task+source+sink entries in one go. The reason I did this is because optimize_step will need to add in additional checks in the final tree it generates, to avoid two task/source/sink names accidentally colliding in the final tree.

So the overall optimize function recursively expands step by step, until optimize_step fails to find something new to expand, and just return its input unmolested. That's the base case.

If the optimize_step output is the same as its input, then we're done and we can return it. Otherwise, recurse and expand another step.

Most of my thinking while writing optimize_step can indeed focus just on the one case it's dealing with to the exclusion of everything else. Assuming that I write the base case properly.
 
Back