lainchan / λ

(untitled)

Shogun · 2025-03-27 11:23
C Programming Thread Post your projects and questions here! Old thread: >>24824 C++ thread: >>38 Fun sites: >https://www.iso-9899.info/wiki/Main_Page >https://www.c-language.org >https://gustedt.gitlabpages.inria.fr/modern-c/ FAQ: > Q: What's the best C programming textbook A: The best C textbook is C programming : a modern approach - Second Edition by K. N. King (http://knking.com/books/c2/) > Q: What about The C Programming Language - Second Edition aka K&R? A: It's very well-written and it has very good example programs and exercises. The problem is that it's a bit outdated. For example, it doesn't tell you about deprecated or dangerous stdlib functions. It's a good second C book, though! > Q: Can you recommend any good video tutorial? A: How about these? https://www.youtube.com/watch?v=e9Eds2Rc_x8&list=PLhQjrBD2T381L3iZyDTxRwOBuUt6m1FnW&index=2 >Q: How to learn to use pointers in C? A: https://github.com/jflaherty/ptrtut13 > Q: What are the recommended compiler flags (CFLAGS)? A: I suggest using these: -march=native -O2 -std=c99 -Wall -Wextra -Wformat=2 You can also add -pedantic to your CFLAGS while you are learning C. > Q: How to get the assembly code that GCC/Clang generates? A: You need to add some additional compiler flags: -masm=intel -fverbose-asm -save-temps > Q: How to write makefiles? A: https://nullprogram.com/blog/2017/08/20/ > Q: What's a good style guide? A: I suggest following the "Sücckless" project style guide or OpenBSD style guide. > Q: Where can I learn about network socket programming in C? A: Check out BeeJ's guide: https://beej.us/guide/bgnet/ > Q: What is the best book for learning Linux programming? A: Either Advanced Programming in the Unix Environment (APUE) or The Linux Programming Interface : A Linux and UNIX System Programming Handbook > Q: Where can I found more libraries for my C project? A: https://github.com/inputsh/awesome-c#readme > Q: How to learn to use the GDB debugger? A: Here is a quick tutorial to get you started: https://beej.us/guide/bggdb/ Also, see: Valgrind (and memory sanitizer) Remember to compile your code with debugging symbols (-g3 )! > Q: Where can I find programming exercises? A: https://exercism.org/tracks/c A: https://projecteuler.net/archives >>39329 >Are there any guides on writing portable C? You need to use feature test macros. >https://man.archlinux.org/man/core/man-pages/feature_test_macros.7.en >https://www.gnu.org/software/libc//manual/html_node/Feature-Test-Macros.html I also suggest using both GCC and Clang. Also, test your serious programs on multiple operating systems (for example, GNU/Linux and OpenBSD).
overriding · 2025-03-27 11:26
do you still have to recompile gcc to target different architectures? that's the only reason why I use clang, I can compile llvm once (with all the targets I want) and then cross-compile from my build system (cmake or meson)
Mitnick · 2025-03-29 00:25
GCC now supports sanitizers: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html https://clang.llvm.org/docs/MemorySanitizer.html
Cowboy · 2025-04-06 10:39
Is there a reason the C standard mandates struct ordering? The general philosophy of C seems to be "let the implementation do whatever the hell it wants so long as the program runs", so I'm curious why they made an exception for this.
renames · 2025-04-06 11:42
>>41597 Pure speculation on my part, but I would guess it's for ABI compatibility. It would be a problem if a program compiled with clang couldn't use a library compiled with gcc.
Masami Eiri · 2025-04-06 15:04
So, I watched this video: https://yewtu.be/watch?v=443UNeGrFoM In this video this guy states that he programs in C89, because C99 is broken and C11 fixes some things, but is too new. Can any C programmer explain what he meant by this? I've been trying to adhere as much to C99 as possible when programming and always set -std=c99 -pedantic in my compiler, as I've been told that this is a good standard to use and adhere to. What exactly is broken in C99? Why would anyone in the anno domini 2025 use C89?
Meddler · 2025-04-06 22:39
>>41597 In early versions of C all struct field names were in one global namespace and calculating offsets was basically all they were good for
The Plague · 2025-04-07 13:13
I'm kind of shocked that it's current year and C doesn't have good, portable filesystem abstractions built into the language. It's added all sorts of other crap that nobody should use like generic selectors (function overloading is a misfeature), but you still need a third-party library if you want to reliably access files across multiple operating systems. And no, I don't think C being low-level is a good excuse. Filesystem access is an inherently high-level operation, one that depends on the presence of an operating system. There's no good reason for C's file i/o to be this crappy.
Molly Millions · 2025-04-07 16:00
>>41605 I'm bored. >They are vulnerable to stack overflows which can cause puzzling segfaults. Stack overflows don't cause segfaults. >Stricter aliasing was introduced in C99 and newer (and C++98 and newer). Nah, it's C89 (cf §3.3 Expressions). >Memcpy has overhead, in some cases being an order of magnitude or two slower than a type-punned equivalent. In all modern compilers, memcpy with compile-time size < an arbitrary limit are just replaced by builtins. Absolutely no overhead in the case replacing pointer or union-based type punning. >You can disable it with -fno-strict-aliasing. Codebases like Linux, and many C/C++ game engines disable strict aliasing to avoid memcpy when they can so they can do UB type-punning instead. You got it wrong. It's usually disabled because to avoid unintended "nasal demons", not for any performance reasons. >>41609 Because POSIX and Win32 (and others) are too different for it to be easy to standardize. What's wrong with fopen/fclose/remove, anyway? Sure, I'd love a portable mmap, but let's not dream.
word · 2025-04-09 11:25
>>41610 >Because POSIX and Win32 (and others) are too different for it to be easy to standardize I was thinking something more along the lines of SDL's filesystem abstractions.
Sabo Engle · 2025-04-10 01:31
>>41601 I want to know this too...
Switch · 2025-04-10 08:47
>>41601 >>41619 He answered that in his comments. The link to his blog seems dead at the moment, but it's still on the internet archive: https://web.archive.org/web/20210622080902/https://news.quelsolaar.com/
Hagbard Celine · 2025-04-11 05:32
>>41621 Undefined behavior in general was a mistake. Implementation defined behavior does much the same thing but is more explicit and consistent.
vector · 2025-04-11 09:48
>>41621 For reference, the actual post is https://web.archive.org/web/20210927220551/https://news.quelsolaar.com/2020/03/16/how-one-word-broke-c/ TLDR: His point is that C99 changed the definition of undefined behaviour to "the implementation can do what it wants". This is bad because it allows implementations to be stupid. I don't really agree with him though. For one, the C89 definition states that the standard "imposes no requirements", so there is no difference with the C99 definition. The listed possibilities are only examples of what a sane implementation would do. His examples are a bit weird too as I am not aware of a compiler in wide use that would misinterpret them like he does. Except the second one, which would be solved by explicitly casting a and b to (unsigned int). > if(p == NULL) > write_error_message_and_exit(); > *p = 0; > If the compiler thinks that writing to NULL, is undefined, it can therefore assume that since you are writing to p, p can’t be NULL. If p can’t be NULL, the entire if statement can be removed and after optimization the code looks like this: > *p = 0; This doesn't even work. There is undefined behaviour IIF p is NULL and the *p=0 statement is executed. Presumably, write_error_message_and_exit doesn't return so this would never happen. The compiler cannot make that optimization. More importantly, C99 does provide useful additions. VLAs, for all their faults, but more importantly FLAs, the intN_t families, snprintf for safer formatting.
Jens Hansen · 2025-04-11 10:52
Is there a major performance penalty for avoiding global variables? I know it means passing way more information into functions, but I've heard that modern compilers are good at optimizing that kind of thing.
Lunch · 2025-04-11 19:52
>>41634 not unless you're worried about using too much of the stack for passing arguments and allocating local variables. that shouldn't be an issue unless you're doing recursive calls or being very dumb about what variables you're allocating on the stack instead of the heap (the short version of "how not do be dumb about this": don't allocate large structures on the stack, e.g. if you have function local variables that are basic data types or small structures, it's fine, but for large arrays or structures that have a lot of components, use the heap via dynamic memory allocation, and make sure to pass large structures around via reference). if you're doing recursive calls, your compiler probably can optimize away a lot of the potential issues via things like tail call recursion and by recognizing that it can recover stack space from some of the function local variables early, but even with recursive calls that are poorly optimized, unless you're using a dumb algorithm that produces a large number of recursive calls (or accidentally causes infinite recursion), it shouldn't usually be an issue (there's a lot more that can be said here, but since the question was about function arguments in general and not recursive functions, i'll stop here). it is technically true that on systems that pass arguments via the stack, using a global variable will be faster than passing a variable around as a function argument, since you won't have to execute an instruction to push the arguments to the stack. but that time difference is minuscule. your performance bottleneck will virtually always be elsewhere and several orders of magnitude greater than the cost of passing arguments. If you're on an architecture where arguments are passed via registers then a global variable will have even less of a benefit, as you'll usually be moving it into a register at some point anyway.
null · 2025-04-12 11:33
>>41638 >using a global variable will be faster than passing a variable around as a function argument Not necessarily, in any big program this will break cache locality and thus be slower than using the stack. >>41634 It depends on the machine and the size of your global variables and parameters, there might even be a performance reward. Modern programs usually optimize for cache hits rather than memory accesses.
Nameless · 2025-04-26 22:35
>>41530 Can anyone suggest C programs with source code that is written lucidly such that it may be understood by a beginner? I want to learn how C is written and used in general.
Dr. X · 2025-04-27 06:58
Do any lainons primarily live in C for all your general purpose programming? I've kind of been avoiding it but am now thinking it may be the best general-purpose option. It's the lingua franca of languages. Do you think people will move away from writing C and instead just interop with it?
Frank Garza · 2025-04-27 08:25
>>41931 The first that come to my mind are: libev (https://cvs.schmorp.de/libev/) but the problem is that it is mostly just glue code around OS features rather than an actual program. BearSSL (https://bearssl.org/gitweb/?p=BearSSL;a=tree) but even if the code is good it is still undecipherable if you aren't familiar with the cryptographic algorithms and optimizations it implements. The FreeBSD libc (https://cgit.freebsd.org/src/tree/lib/libc) but this is completely different from the C code you normally see in programs. The Minix GTK and it's descendants are easy to read if you are familiar with GLIB and GTK itself but they are otherwise incomprehensible gibberish. Raylib is readable but unrelated to regular C. To be honest I don't think C can be readable ever (without domain specific knowledge) and that doesn't seem to bother C programmers. Try reading programs that you are already familiar with. The main obstacle is that every big project has it's own preprocessor "clever" practices and tricks, and it's own combination of make/autotools/shell/cmake, but it is doable.
Linda Lee · 2025-04-27 10:33
>>41938 >every big project has it's own preprocessor "clever" practices and tricks Yeah, I think the preprocessor feels good only when you are the one that uses it in code that you wrote yourself, such that you know exactly how it fits in the program; but whenever you encounter it in a foreign codebase it quickly devolves into a pain in the ass, especially when macros start having eachother in their definitions creating unreadable nested abominations.
array · 2025-04-29 03:23
>>41931 suckless is generally pretty simple, for example: https://git.suckless.org/sbase/files.html musl libc is also reasonably simple, though it does deal with fairly low level details https://git.musl-libc.org/cgit/musl/tree/
Frank Garza · 2025-04-29 05:33
>>41948 suckless has short programs but that doesn't represent "how C is written and used in general". Their stuff is usually 2 to 10 kloc gluing together either xlib calls or the unix/linux syscalls and utilities. If anything, the point of suckless is that they are different, not that they are the average. They are a good learning resource if you want to learn how to write suckless programs, but not if you want to learn how C is commonly used in the wild.
Boris Grishenko · 2025-05-10 04:04
Hi lainons, I'm trying to write a readline style library for reading line input but with extra features. The reason why I don't want to use GNU readline is that it does a terrible job at handling grapheme clusterization. But that's not readline's fault entirely, because it's very difficult to know how the underlying terminal is going to handle graphemes. I can't just slap a unicode library for that because the underrlying terminal might not even know about graphemes and render each codepoint in their own cell. The way I plan on doing this is by maintaining a separate buffer whose goal is to store geometry data for the input buffer. Everytime a codepoint is inserted I try to update a 'cluster' array. What I mean by cluster is grapheme but not in the unicode sense, in the sense of what the target terminal is actually going to display. E.g on xterm '1f9d1 200d 1f33e' (the farmer emoji), will be displayed as '1f9d1 1f33e' (zero width joiner being invisible). My algorithm should treat that as three clusters (a zero-width cluster for the zero-width joiner) On more modern terminal, grapheme clusterization is going to follow what the unicode specification says. Essentially I store that sort of information in this structure: c struct cluster { size_t size; /* byte size of cluster */ int width; /* column width of cluster */ }; Which I maintain an array of. My main issue is how I should maintain and update this array. I have devised an algorithm but it's pretty whacky and almost seems like an evil hack of some kind. When a new codepoint is inserted at (codepoint) index k. My algorithm tries to find indexes i <= k < j such that Width of the entire input buffer - Width from 0 to i - Width from j + 1 to the end = Width from i to j What I mean by width of a string is this: - Go to a spare line, at column = 1 - Write the string - Get the new cursor position (E.g write \033[6n and read the report from the terminal) - Return the difference, that should be how the terminal performs clusterization of the input string If the terminal supports invisible text then I can make this process invisible to the user. And given that I'm going to redraw my prompt after updating the clusters, I can use the current line for doing measurements. I'd like to get your thoughts on this. If you know of a simpler method then please share it because the current algorithm is just a mess at this point. In my screenshot, in xterm the zero width joined is considered part of the second cluster. This isn't a big issue right now but I hope you get the idea of what I'm trying to do.
Trinity · 2025-05-10 04:33
>>42090 >What I mean by cluster is grapheme but not in the unicode sense, in the sense of what the target terminal is actually going to display. Why not strictly follow what Unicode says? I don't see an advantage in trying to handle such things on a per-terminal basis. I think an easier option would be to treat it as a single cluster unconditionally and use custom highlighting to select the whole cluster. E.g. picrel using ANSI codes ('\e[7m' to invert, then '\e[0m' to reset)
Trinity · 2025-05-10 04:44
>>42091 The issue is that this is for geometry calculation. I want the library to have flawless unicode handling. Even if the terminal doesn't. For instance, if you press backspace once and your terminal handles grapheme properly, that should delete the whole grapheme. But if it doesn't then it would be smoother to delete 1 'cluster' at a time. Another issue is that functions like wcwidth are unpredictable when the codepoints come from reserved unicode ranges, which the terminal might treat as wide or as normal.
Junichi Fujiwara · 2025-05-13 02:28
>>42090 Here's a follow-up. I've found a way to do this almost cleanly. c /** @brief Performs expensive reclustering of all codepoints in [lo, hi-1] */ static void recluster_precise(t_getline *line, t_buffer *buf, t_u8_iterator lo, t_u8_iterator hi) { const char*str = buf->buffer.str; size_ti[3]; intw; t_cluster*c; c = &buf->s_clusters.data[lo.cp_pos]; w = getline_text_width(line, str + lo.byte_pos, c->size); c->width = w; i[0] = lo.cp_pos + 1; i[1] = lo.byte_pos + c->size; i[2] = lo.byte_pos; while (i[0] < hi.cp_pos) { c = &buf->s_clusters.data[i[0]++]; if (getline_text_width(line, str + i[2], i[1] + c->size - i[2]) == w) c->width = 0; else { i[2] = i[1]; w = getline_text_width(line, str + i[1], c->size); c->width = w; } i[1] += c->size; } } This function is very expensive, so I only call it under certain conditions. These conditions may change in the future. But it does it's job well (pic related)
limited · 2025-05-14 21:58
How do environment variables work in Linux? Im writing a basic shell in C and have reached the point where I finally run external programs. unistd.h gives some functions like execv which im using In the examples ive found online, the first argument to execv would be the command name, and it would search the $PATH for the executable. Up until this point ive assumed that environment variables are handled by the shell. I implemented them using a hash table. How would functions like exec() or execv() work without a shell to provide the $PATH? char *arg[] = {"ls", "-l", NULL}; pid_t pid = fork(); int status; if(pid < 0) { perror("Failed to fork()"); } else if(pid == 0) { execvp(arg[0], arg); } else { waitpid(pid, &status, 0); return WEXITSTATUS(status); } Similarly, ls runs from the current directory, which id also assumed would have to be kept track of by the shell. I guess if it works that's what matters, but now im curious. Anyway thanks and sorry for dumb question
Colmerauer · 2025-05-14 23:38
>>42115 The actual execve syscall (man 2 execve) doesn't iterate through the PATH environment variable to find the executable. The libc execlp(), execvp(), execvpe() functions are what handle iterating through the PATH environment variable to find an executable. The execve syscall and some of the libc exec functions can provide the `extern char **environ;` that is available to the child process. `chdir` and `getcwd` can change and get the current working directory. Each process on linux, not just shells, have access to environment vars `extern char **environ;` and the current working directory `chdir` `getcwd`
Crash Override · 2025-05-15 10:50
>>42115 I did the same thing not too long ago. When you run commands bash does these things: 1. If the command has a `/` in it's name, then locate executable from the current directory -> if found run program 2. Check if the command is a function -> if found run function 3. Check if the command is a builtin -> if found run builtin 4. Otherwise try to locate executable in the $PATH. Bash does keep a cache of which executables are in the $PATH. Bash uses a hash table to store the list of available executables, but any other data structure is fine. It first searches in this cache. If it's not found, then it will clear the cache and repopulate it by walking each ':'-separated paths in $PATH (then perform lookup again) Only then it will emit an error and stop executing the command if the executable is not found. Here's the relevant bit from bash's documentation: https://www.gnu.org/software/bash/manual/bash.html#Command-Search-and-Execution Bash's source code is a great source of informations (I used it extensively for my grammar). Here's an extract from findcmd.c#336 /* Look for PATHNAME in $PATH. Returns either the hashed command corresponding to PATHNAME or the first instance of PATHNAME found in $PATH. If (FLAGS&CMDSRCH_HASH) is non-zero, insert the instance of PATHNAME found in $PATH into the command hash table. If (FLAGS&CMDSRCH_STDPATH) is non-zero, we are running in a `command -p' environment and should use the Posix standard path. Returns a newly-allocated string. */ char * search_for_command (pathname, flags) const char *pathname; int flags; `execve()` is the way to go. You'll need to feed it the environment variables from the shell. Bash defines special builtins, like `continue`, `break` `return` etc. These (I believe), are resolved before functions, but they have special meaning depending on the evaluation context. Good luck with your shell anon. I'm currently working on my own readline, which is pain in the ass to do.
John Rain · 2025-05-15 11:26
>>42118 The way the shell keeps track of it's environment is not important, as long as the programs ran from the shell have the correct variables set. // your shell's main int main(int argc, char **argv, char **envp) envp is a NULL-terinated array of string like argv. It has `varname=value` entry for each environment variable set when the shell was executed. Usually the shell will move these strings to a more suited data structure (like a hashmap or bst). All you need to do, is to provide an envp array when you call `execve`. In some cases it gets a little messy, bash lets you have prefix-assigned variables. e.g `VAR=VALUE`. These are handled in a more complex manner and will shadow exported variables. When an executable follows a prefix assignment, the prefix assignment happens inside the fork for the executable, therefore it does not affect the rest of the shell. If no executables follow, then the variable is set for the duration of the shell session, but it won't be exported to execve. E.g try doing the following and see for yourself: VAR=A env | grep ^VAR # nothing shows up export VAR=B env | grep ^VAR # 'B' VAR=C env | grep ^VAR # 'C' VAL=TEST echo $VAL # nothing shows up, because $VAL is resolved BEFORE the prefix-assignment `VAL=test` echo $VAL # nothing, since the previous prefix-assignment happened inside a fork VAL=TEST env | grep ^VAL # 'test' since variable $VAL was set for the execution of `env`
Nell · 2025-05-15 13:43
>>42115 >How do environment variables work in Linux? The other anons gave good answers, but I figure it might be interesting to know the Linux-specific ABI too (though it won't be relevant for C userland). If you run any program with gdb, set a breakpoint on _start and run it like so: gdb path/to/program ... (gdb) b _start (gdb) r some random arguments Then, take a look at what is on the stack (gdb) x/4gx $rsp 0x7fffffffdd40: 0x0000000000000004 0x00007fffffffe0f6 0x7fffffffdd50: 0x00007fffffffe14a 0x00007fffffffe14f First thing that stands out is the 0x4: that's evidently the argument count. Right after that are some values that look like stack addresses. Let's look at 0x00007fffffffe0f6: (gdb) x/100c 0x7fffffffe0f6: 47 '/' 104 'h' 111 'o' 109 'm' 101 'e' 47 '/' 109 'm' 101 'e' ... 0x7fffffffe146: 101 'e' 112 'p' 108 'l' 0 '\000' 115 's' 111 'o' 109 'm' 101 'e' 0x7fffffffe14e: 0 '\000' 114 'r' 97 'a' 110 'n' 100 'd' 111 'o' 109 'm' 0 '\000' 0x7fffffffe156: 97 'a' 114 'r' 103 'g' 117 'u' Those are our program arguments as a blob of strings, starting with the program name. But typically arguments are a list of pointers, so where is that? Let's try some other addresses: (gdb) x/100c 0x00007fffffffe14a 0x7fffffffe14a: 115 's' 111 'o' 109 'm' 101 'e' 0 '\000' 114 'r' 97 'a' 110 'n' ... (gdb) x/100c 0x00007fffffffe14f 0x7fffffffe14f: 114 'r' 97 'a' 110 'n' 100 'd' 111 'o' 109 'm' 0 '\000' 97 'a' ... Turns out that list is on the stack itself. Let's take a look past it: (gdb) x/8gx $rsp 0x7fffffffdd40: 0x0000000000000004 0x00007fffffffe0f6 0x7fffffffdd50: 0x00007fffffffe14a 0x00007fffffffe14f 0x7fffffffdd60: 0x00007fffffffe156 0x0000000000000000 0x7fffffffdd70: 0x00007fffffffe160 0x00007fffffffe1a7 As expected, the argument list ends with a NULL. What are the pointers past that? x/100c 0x00007fffffffe160 0x7fffffffe160: 83 'S' 72 'H' 69 'E' 76 'L' 76 'L' 61 '=' 47 '/' 103 'g' 0x7fffffffe168: 110 'n' 117 'u' 47 '/' 115 's' 116 't' 111 'o' 114 'r' 101 'e' 0x7fffffffe170: 47 '/' 110 'n' 115 's' 118 'v' 107 'k' 110 'n' 121 'y' 99 'c' 0x7fffffffe178: 52 '4' 55 '7' 120 'x' 114 'r' 106 'j' 97 'a' 57 '9' 54 '6' 0x7fffffffe180: 56 '8' 110 'n' 55 '7' 103 'g' 57 '9' 108 'l' 108 'l' 99 'c' 0x7fffffffe188: 100 'd' 114 'r' 102 'f' 105 'i' 120 'x' 104 'h' 122 'z' 106 'j' 0x7fffffffe190: 52 '4' 45 '-' 98 'b' 97 'a' 115 's' 104 'h' 45 '-' 53 '5' 0x7fffffffe198: 46 '.' 49 '1' 46 '.' 49 '1' 54 '6' 47 '/' 98 'b' 105 'i' 0x7fffffffe1a0: 110 'n' 47 '/' 98 'b' 97 'a' 115 's' 104 'h' 0 '\000' 87 'W' 0x7fffffffe1a8: 73 'I' 78 'N' 68 'D' 79 'O' 87 'W' 73 'I' 68 'D' 61 '=' 0x7fffffffe1b0: 50 '2' 51 '3' 48 '0' 54 '6' 56 '8' 54 '6' 56 '8' 52 '4' 0x7fffffffe1b8: 0 '\000' 79 'O' 66 'B' 74 'J' 67 'C' 80 'P' 76 'L' 85 'U' 0x7fffffffe1c0: 83 'S' 95 '_' 73 'I' 78 'N (gdb) p (char *)0x00007fffffffe160 "SHELL=/gnu/store/nsvknyc47xrja968n7g9llcdrfixhzj4-bash-5.1.16/bin/bash" Looks like our environment variables are also on the stack as a string blob, and there is another null-terminated list on the stack which is our `environ`/`envp`. Note that these blobs normally don't get updated directly. Instead, the libc makes a copy of it when necessary (though I'm not sure of the precise details).
Wanderer · 2025-07-27 09:20
When dealing with arrays what do you prefer to do; Writing one array struct type for each type (can be done with a macro) or having an Array without a type and casting it to the type each time you need to access to it? To remember the type you have to or remember it or to name the variable with something that tells you what type it is. struct ArrayFloat { size_t len; size_t capacity; float* data; }; struct Array { size_t len; size_t capacity; size_t elem_size; void* data; }; #define array_get(type, arr, idx) \ (*(type *)((uint8_t *)(arr).data + (idx) * (arr).elem_size))
Naomi Nascimento · 2025-08-06 01:27
>>42668 I like to use anonymous struct for that struct { float *data; size_t len; size_t capacity; } float_array; In fact, you can do something like this: #define array(type) struct { type *data; size_t len, capacity } #define array_reserve(array, new_cap) do { \ if ((array)->len >= (new_cap)) break; (array)->data = realloc((array)->data, (new_cap) * sizeof((array)->data[0])) (array)->capacity = (new_cap); } while (0) array(float) buffer; array_reserve(&buffer, 64); buffer->data[63] = INFINITY;
Wanderer · 2025-08-09 13:06
>>42734 Thanks. Seems a better way to do it but I would love if I didn't have to use macros all the time because debugging them is horrible
St. IGNUcius · 2025-08-10 14:18
For personal projects, I've gradually adopted a style of code-formatting that is highly readable to me but is also utterly bizarre next to typical C code. Here's how most people would write a program #include<stdio.h> int main() { int a = 1; float foo = 2.0; unsigned int example = 3; for(int i = 0; i < 10; i++){ if(i < example) printf("Hello\n"); else printf("World!\n"); printf("%i\n", i); } return 0; } And here's how I do it. #include<stdio.h> int main(){ int A = 1; float Foo = 2.0; unsigned int Example = 3; for(int I = 0; I < 10; I++){ if(I < Example) printf("Hello\n"); else printf("World!\n"); printf("%i\n", I); } return 0; } I feel like a fuarrrking lunatic for doing it this way, but I unironically find my way of doing things easier to read, and I don't fully understand why. Does anyone else wave weird programming quirks like this, or am I just insane?
Cypher · 2025-08-10 14:27
You're insane, and you have a very wide monitor
Dujour · 2025-08-10 17:06
>>42755 For what it's worth, I have Emacs set up so it automatically centers the current block. I would imagine that deeper levels of indentation would get very hard to read without a text editor that's configured to navigate them for me. I do have a wide monitor though.
typedef · 2025-08-11 07:11
>>42754 I did some weird soykaf too. At school, they imposed a retarded linter tool. The code norm that the linter tries to enforce is not that retarded, e.g it pushes for readibility, limited number of variables, tries to prevent spaghetti code etc.. due to all these constraints, when my code reaches say 35 lines, but the linter says no more than 25, instead of splitting into multiple functions/files, I just shorten until it's an unreadable 25 lines mess. /* Get the gnl data for a file descriptor, either by retrieving already existing * data, or by creating a new one */ static struct s_gnl *get_data(int fd) { size_t i; if (fd < 0) return (NULL); i = 0; while (i++ < __gnl()->size) if (__gnl()->data[i - 1].fd == fd) return (&__gnl()->data[i - 1]); if (__gnl()->size >= __gnl()->capacity) { __gnl()->data = __gnl_realloc(__gnl()->data, __gnl()->capacity * sizeof(struct s_gnl), ((__gnl()->capacity + !__gnl()->capacity) << 1) * sizeof(struct s_gnl)); if (!__gnl()->data) return (NULL); __gnl()->capacity = (__gnl()->capacity + !__gnl()->capacity) << 1; } i = 0; while (i < sizeof(struct s_gnl)) ((unsigned char *)&__gnl()->data[__gnl()->size])[i++] = 0; __gnl()->data[__gnl()->size].fd = fd; return (&__gnl()->data[__gnl()->size++]); }
Undisclosed · 2025-08-11 07:30
>>42757 I forgot to mention that the linter program is poorly implemented. They parse C using regular expressions, and some of their expressions are straight up incorrect. For instance: int n = 0xb0b doesn't parse as an hex literal, but as a binary decimal, with an invalid character `b`. That's because they use `0[bBxX]*` to attempt to detect incorrect prefixes like `0xx` or `0bB` but it backfires in these specific example. There are also some weird cases where the linter goes into an infinite loop, like if you forget to add a `;` after a function call. I'm not against coding standards, but if you enforce them with an automatic tool, you better make sure it does the job correctly. I could re-write their entire linter using treesitter or flex+yacc and it would take me just a few days.
Tank · 2025-08-19 01:12
Hello! I am somewhat familiar with C, but I have no experience with POSIX threads. Are there are any recommended /lain/ approved resources that teach pthreads?
protected · 2025-08-19 17:49
Why do people write int *foo rather than int * foo The * is, in my mind, a qualifier. It isn't part of the variable's name, like with Perl and its sigils. I know people say "declaration reflects use", but then int * const foo comes along and throws that out the window.
Liet Kynes · 2025-08-20 00:38
>>42797 Because the standard is like kicking dead whales down the beach. int * foo, bar; declares a pointer and an int, not two pointers.
Leto Atreides I · 2025-10-10 11:49
When I need to do range-checked math on signed ints, I typically cast to unsigned ints, perform some calculations based on the signs of the inputs, check if the result overflows and/or is outside the range of INT_MIN/INT_MAX, and then act accordingly from there. There's probably a much better way to do this, but I'm not quite sure what it is. If you guys have any suggestions, I'd love to hear them.
Six · 2025-10-10 12:59
>>43295 -fwrapv to avoid committee braindamage. And/or helper function with whatever works.
renames · 2025-11-03 18:29
>>43295 Both GNU C and ISO C23 have macros for checked integer arithmetic. #if __STDC_VERSION__ >= 202311L #include <stdckdint.h> #endif #if __STDC_VERSION__ >= 202311L if (ckd_add(&z, x, y)) #elif __GNUC__ if (__builtin_add_overflow(x, y, &z)) #else #error No checked arithmetic functions #endif fputs("Overflow occurred\n", stderr); printf("z = %d\n", z);
Meddler · 2025-12-01 01:57
Is there a good way to log performance-critical code in C? So far, the projects I've seen either use massive custom loggers which are lots of work to upkeep (such as openssl) or they use some cursed macros like so: C #define LOGMESSAGE(LOGLEVEL) if(LOGLEVEL<=g_logLevel) fprintf(stdout, The reasons why inline functions can't be used is that varargs functions are just not inlined by most compilers because it's hard to do, I guess. The alternative, custom loggers like openssl, often make different log macros for each function with their own argument types, or something like that. It's all very tiresome.
Koos van der Merwe · 2025-12-01 18:40
>>43591 That'd depend a lot on what the rest of your program is like. If it's single threaded (or fewer threads than CPU cores), the best option would be to have a parallel thread that does the logging to which you send logging requests using a lock-free single-consumer queue. Since the most expensive part of the logger is by far the write call, it helps to not have that interfere with the worker threads at all. The queue should be encoded as an array with atomic integers giving its head and tail. To send a log request, a worker would (a) increment-and-modulo the tail integer, (b) use sprintf to write its message into a buffer in place within the queue and finally (c) set an atomic flag or call post on a semaphore (can't tell which would be faster) to signal the logger the request is done being written.
The Oracle · 2025-12-01 23:05
>>43591 #define print(LOGLEVEL, ...) if (LOGLEVEL<=g_logLevel) logfn(__VA_ARGS__) And you could swap this out for an inlined function on inline supported compilers. Or with different functions: #define print(LOGLEVEL, LOGFN, ...) if (LOGLEVEL<=g_logLevel) LOGFN(__VA_ARGS__) Using compiler specific extensions to force inlining functions may also work, like in g++: __attribute__((always_inline)).
Roy Batty · 2025-12-02 20:13
>>42120 Interesting little post thank you lain
limited · 2025-12-10 01:54
>>43608 Thanks, I didn't realize ... and __VA_ARGS__ worked like that in macros, I blame the docs of the compiler I use...
LeemyLeeLeedinsky · 2026-04-01 09:46
(me in the left, feeling mighty and powerful because I use C) ...Leaving bad jokes aside, a lot of developers say that, quote "C is a bad language." Some people go as far as saying that the developers didn't knew what they were doing (suuure thing, Dennis Ritchie is known for being mediocre regarding computer science). But, when you confront these folks with a counterargument or just ask em' "what is your basis for validating what you just said?" they don't know how to answer, act elusive or straight call you a 'retard' (this generation's favorite fallacy: attacking you instead of answering the question). In all seriousness: What are the objectively weak points of C compared to other languages? I beg you, things like "it's a 50-year-old language!" (Java has 30 years and I don't see anyone complaining about that) or "it's not safe!" are not valid because they are trivial to refute. I try to be agnostic about the tools I use, but C has a special place in my heart; I really enjoy using it and have learned more about computers with it than with other languages. By the way, thank you so much for the provided resources at the top. I've found such a goldmine.
Void · 2026-04-03 07:58
>>44048 I used C as my primary language for a very long time and it still holds a soft spot for me, but it's ignorant to avoid recognizing its flaws like this. There's a reason I moved into functional programming as I outgrew the language. >Java has 30 years and I don't see anyone complaining about that Then you aren't really looking very hard. Language design has improved quite a bit since the 60s (although prominent languages are too busy insider trading old soykaf instead of modernizing). Plenty of people call out Java for its cruft that comes with age, same as C. >"It's not safe!" is not valid...easy to refute. Given the data points towards memory errors overwhelmingly being the cause of major vulnerabilities as the internet has taken over, I'd be curious as to your refutation. If it consists of "user/programmer error", then you're simply being intentionally obtuse as that falls squarely into subjective analysis. If you want to be objective, accept the data: https://www.microsoft.com/en-us/msrc/blog/2019/07/a-proactive-approach-to-more-secure-code > What are the objectively weak points of C compared to other languages? The only true objective measurement that I'm aware of is the one linked above, but it is a fairly damning one towards all MMM languages. As for my personal issues with C (which have varying levels of objective and subjective components): 1. The poor syntax for controlling the semantics of memory is an issue. Constant and restricted pointer types can mean drastically different things depending upon written order, which can have a use, but it is a horrible way to declare these constraints and leads to confusion. Languages like Odin and Zig handle manual memory control in a much less complicated manner. 2. The horrible type system. For example: Allowing for blind conversions between pointer types is terrible practice, and leads to all sorts of issues. It's a dynamic language with the illusion of type safety, much like Java. Additionally, ambiguous types are also an issue. For example: What is the minimum size of an "int" type? How about a "long"? If you said 32 and 64 bits, you're incorrect. The specification allows them to be 16 and 32 bits respectively. This is why "stdint.h" exists, because C's standard types are frankly insulting to the programmer. See languages like Idris or Haskell to see how powerful and meaningful type systems can actually be. 3. Poor standards. I historically preferred to lock myself to ANSI C (ISO C89) when using it, but then you're locked to horrible global state systems that never thought a CPU would have multiple cores. As a result I eventually accepted using ISO C99, which adds both proper standard library functions, and many problematic and "implementation dependent" features into the spec. VLAs are a high profile example, but the C steering committee spent decades course correcting to remove a lot of the other undefined behaviors they allowed in C99. This historical cruft still haunts C, referring back to the age of the language. This is an issue all languages have as they age. Hell, look at the complexity bloat of Haskell, a language I otherwise quite like. All this said, I still love and do use C to do write support libraries in my predominantly functional programs. Usually to simplify FFI to other libraries, but sometimes just because its easier (and more performant) to relegate something to C. I tried Odin, Zig, Rust, etc, and while they undoubtedly have better syntax and semantics, they simply fail at the thing C is the best at: Being ubiquitous.
LeemyLeeLeedinsky · 2026-04-03 12:01
>>44056 I promised to myself not to post or reply anymore, but you are pretty civil and I like you because of that, so I think it would be polite from my part to reply to you: >Java has 30 years and I don't see anyone complaining about that I'm aware that there are indeed people who complain about Java's veteran status and, believe it or not, I've even seen complains about Python being from the 1991! (if my memory does not fail). What I wanted to say, I will try to rephrase it as an analogy: A lot of people complain about pineapple in pizza, but you don't see much people complaining about anchovies even thought there also exists pretty much people who complain about these little fellas on their pizza. 'C' is the 'pineapple' and 'Java' is the 'anchovies'; complaining about C's age is too generic from my perspective, to the point that it already sounds like a bad pun. But I think you are the fine example of a clever person who does not use generic stuff to complain about a tool. Sorry if my analogy is too dumb, but it was the only way I could more or less explain what I wanted to say regarding that. I don't like the use of the age as a negative feature towards a tool because, in my personal opinion, the fact that a faulty decision was made in the design of a language is an opportunity to improve it and evolve. People learn more from mistakes than from success, and of course this also applies to modern languages. Probably in 20 years people will complain about an obsolete decision in Rust's or Zig's design. Standards keep evolving and getting better according to the current necessities of developers. >I'd be curious as to your refutation. >If it consists of "user/programmer error" Yes, pretty much this, but I swear I'm not trying to make the generic "you just don't know how to use the language" comment. Here's my argument to (try to) validate my point of view: I think modern developers (I'm not that old myself so I sound obnoxious saying that) are way too uneducated about the usage of basic debugging tools such as GDB or Valgrind. I like doing retro stuff and my professor never stops empathizing that, more than the language, using the correct debugging tools is a must to master any form of speaking to a computer. Seriously, Valgrind is such a miracle; it helps you to catch most silly errors regarding memory management. Oh, also, I've seen most developers do not use compiler facilities such as -fsanitize=address (assuming compilation under GCC front end). And don't make me begin about assembly language (Z80 is pretty fun to work with, though). I absolutely believe that there must be a point where a developer should learn basic but imperative details about computers; for example, how the processor and RAM interacts with each other or the computer instruction magnitude order; even if they just do, e.g. back end development. It's not a C, C++, Rust, Zig or whatever language issue. Too many developers are way too focused learning a language instead of learning how a computer works. >accept the objective data I must admit that this is merely a bias of mine, but I do not trust big tech, even less when they just bought a product that was successful and just add Microsoft's™ at the end. Then again, a silly bias of mine, but I promise I will try to find and study other sources so not to make biased, uneducated opinions. If I must provide an argument, Cloudflare's incident in which they killed most of the network infrastructure is a proof that it does not matter if a tool tries to solve all the known problems in software design; if a lazy or mediocre developer is on charge, it will probably fail. It was neither Rust's or SQL's fault, the issue was probably that some irresponsible developers thought that these tools would make all the job for them. Regarding your personal issues about C, I think most of them are valid and on point. I must admit that C has some annoying things that make me always try to use C++ if possible (e.g. the lack of namespaces on the former or the fact that a lot of C stuff is "implementation defined" instead of standard practices). I agree that the syntax issue is mostly subjective, but sadly a lot of coders like to use that as basis for saying that a language is "bad," like the people who complain about LISP just because of the parenthesis. I feel more comfortable and prefer the procedural paradigm, though, so I enjoy niche languages such as FreeBASIC or Pascal. Anyways, thank you so much for your polite and elaborated reply. I genuinely love every opportunity to question why I expressed an opinion in the first place. Hope things are going excellent for you!
Spike Spiegel · 2026-04-03 16:53
>>44059 I appreciate the reply! Believe it or not, this place used to be much more civil than it is now lol. >complaining about C's age is too generic I couldn't agree more with this point, and you're certainly correct that people will use the same criticism of age for these modern "C replacements". I found your analogy quite good, and breaking into a separate line of thinking: Programming has become a simulacrum of "engineering", it is no longer considered science. As a result of this people make the (intuitive in engineering) leap that age comes to equate degradation. But the simple truth is that programming is a formal science, like math, and age instead just means it likely does not have ideal support for modern developments within the field. This is a much, much smaller problem. >Valgrind is a miracle Until you use a libc or runtime that constantly flags it, then it becomes instantly worthless. I absolutely love Valgrind, but its limitations are quite apparent if you've ever worked on C outside of the ideal workflow. I agree with your overall point though: Languages with weak guarantees, like C, need very strong debugging tools. >language vs. computer I'm of two minds about this. On one hand I agree, programmers must know how computers work, as that influences practical decisions around performance and security (e.g. cache locality, something far too few people know of). However, I also disagree that it should be the focus. Computer science, and programming, can be done without computing machines; we only use them because its convenient, and fast. We should be leveraging these behemoth machines we have in our hands to give programmers real programming languages, and only need to relinquish to machine dependent languages like C/Rust/Odin/etc when we need performance as a paramount quality. Even then, in a proper modern toolchain you'll find GC languages that can solve complex problems at C-level performance (most notably: SBCL in Common LISP land). The issue with this is not in the idea (programming languages are, imo, the most important component of computer science), but the fact that most people learn languages that are built upon archaic language design. JavaScript, Java, C, Python, etc are all built upon design ideas that are simply poor imitations of either how humans think, or how machines compute. Instead we should be building more advanced languages that allow programmers to express ideas in sound ways. This is why I love Idris, since its dependent type system allows me to effectively prove the correctness of my code in the type system itself. No unit tests needed, as nothing can fail so long as it compiles at all. >I do not trust big tech It's not a silly bias, but its one that needs checked sometimes. Big tech is horrific, but simple secondary research studies such as that one are not the kind of studies they are stretching the truth with. Psychological or sociological studies such as "AI is making devs more productive" are the recent example of studies they intentionally mislead with. There's a reason why GitHub studies all claim massive gains in productivity, while all independent studies find the exact opposite. >It was neither Rust's or SQL's fault Very correct! Removing memory safety issues doesn't magically usher in a perfect, bug free world. But it will reduce the scale of them (in the security space, at least). That's the importance of true objectivity, it doesn't mean we should abandon C. >C++ vs C, preferential stuff. I wish C++ wasn't such a bloated mess. It's the only language I get lost in. I agree though, way too many programmers are quick to argue that their subjective preference has some form of objective measurement behind it, when it really is just what they prefer. I work with the ACM and quite frequently I find myself having to justify LISP or Haskell derived syntax because most people have only programmed in Python, Java, and C. So I understand the exhaustion when people complain that something just "feels" wrong, as its a meaningless assessment. Also I'd recommend Odin if you like Pascal! It was the only "modern C" that I didn't hate, and it is very Pascal derived. I'd probably use it for my FFI if the Idris compiler toolchain supported it. Thank you for your genuine reply! I've been on this board less and less over the years as these sorts of real discussions have become far too uncommon. Hope things are well for you also!