Arguably this isn't C's "mistake", the issue is that C needed to be backwards compatible with B at the time, B didn't have arrays, it had "vectors" where it was just a pointer that pointed to a memory block. For B, arrays and pointers were the exact same. This is also why you can't just directly assign arrays in order to copy them. You _can_ however get both behaviors by wrapping arrays in a struct. Structs let you directly assign each other to copy and struct sizes are known at compile time. Moral of the Story: Backwards compatibility can hurt you badly.
True words. One could even argue that the root of all evil (in programming) is (bad) legacy. Be that Be that BadThingsTM that you propagated into the future or GoodThingsTM that were left out of the future because of legacy. One could also argue that the whole x86 both hw and sw is a clusterfsck of bad legacy, and the only good thing to do looking forward would be to design a new (proper) architecture and run legacy emulated/virtualized. Pains me deeply having to admit that, for all it's faults, Apple was the only (major) player to consistently tell the eco to "put up or shut up". And lo and behold, they put up and moved onward/forward.
Zortech was my first compiler. It came free with a C book my parents bought me in middle school. I hadn't even thought about that compiler in decades. Thanks for the shot of nostalgia!
@@DJCloudPirate I like to say that Walter Bright taught me C. I read every line of source code that came with that compiler and database supliment. I even printed out all the source files. It was just over a case of wide fanfold paper.
@@SVVV97 true. C++ fixes this by allowing for syntax like enum MyEnum : char {A, B, C}; But it is kind of dumb that C does get updates here and there and yet it took so long to get custom sized enums.
I only have limited C experience but I dealt with this problem by assuming arrays just don't exist, which isn't strictly true, but is enough to eliminate confusion. From then on, if I wanted a proper array I would define my own struct. I couldn't do much about null except programming very defensively, so that felt like a much more significant problem.
3:30 you can use a pointer to an array and its expressed as int (*foo)[N]. In fact, you can use the VLA syntax to do this: void add(size_t size, int (*foo)[size]);
It's time to create the C** programming language, with array contents that start at A[1] and A[0] that contain the array dimention, to give new programmer more ways to fuck their code and segfault because of an unintentional change in A[0]. To make things more complicated, if A[0] does not contain enough space to store the entire lenght of the array, the compiler can randomly choose between transforming A[0] to a pointer to a bigger container or expand A[0] with A[1] without noticing, and it should be inpossible to know which of the 2 happened until a segfault happens
You should really have pointed out who Walter Bright is here -- he's the guy who created the Zortech C++ compiler. I also want to say. he wrote Turbo Linker.
and the zortech c++ compiler was/is the first commercial c++ compiler for Windows, and maybe the first c++ compiler ever if you don't think transpilation to c counts.
Shoutout to Mr. Bartosz Milewski! A great academic/programmer renowned for teaching category theory to programmers. Definitely check him out if you're interested :)
Odin has effectively this exact feature, and even uses it with the old array syntax (T[], but you have to pass the array as f(x[:]) to give the length too, see "slice")
I think null is a bigger problem when you venture outside of C; in C, yeah, it causes some bugs, but it's a fundamental part of how things work. In Java, for example, a fallible function is not just unidentifiable from its type signature, but also failure can be either by raising an exception or returning NULL or whatever, and since objects are implicitly references, there's less clarity about which things can even potentially be NULL vs which things are safe, etc. In C, errors are done through error code returns and writing output to an argument pointer or specific value returns, and you can tell which it could potentially be from the declaration, so you have to deal with much less.
I agree, if you know about null you can totally control it, but requires to build good habits, and people have zero idea about how habits are made or destroyed.
@@laughingvampire7555 Its the age old "This language is great because you have to learn to be disciplined to use it" vs "If a language requires you to be disciplined to use it, it isn't great".
@@PoorlyMadeSweater every professionalist has to have some sort of discipline. Would you like to have undisciplined construction workers building your house? IOr undisciplined car engineer designing you car? I wouldn't. In many professions you have to be disciplined otherwise you are risking your or somebody else's life. Only in computer programming people think that language and compiler has to save them from every mistake.
@@fr0zi Yeap, thats one side of the argument. The other is the human brain isn't as capable as people like to think. Trying to entertain every possible mistake you could be making while maintaining a mental image of the entire system is impossible. Every mistake the compiler cant prevent is a land mine every pro is just one hour of missed sleep away from making. To use your metaphor, a *safe* language would require a buzz saw to contain a SawStop, a device that stops the blade when flesh is detected. A disciplined wood worker wouldn't argue against such a device simply because you *should* be good enough to not need one.
To me the biggest issue with C is the compilation process, the way the headers combine (and need to be manually created and maintained) and have name and macro collisions, additionally complicated thanks to lack of namespaces. Then everything else like: - switch requiring break (inherited by many other languages unfortunately) - null terminated strings - no array length.. - Edit: variables uninitialized by default - implicit casts, not just pointers and arrays - lack of bools for conditions like if (int) being legal - shadowing (very easy to have nested for loops with same "i") ..etc..
Honestly yes, keeping headers up to date is an annoying extra step that is easy to forget. I've run into some pretty cryptic bugs when changing the type of an argument from int to size_t or something because I forgot to update the function prototype in the header. The lack of namespaces is also very annoying and practically discourages you from relying on libraries lest they clash with each other. Maybe that's why C kinda has the reputation of "you need to build all your tools from scratch". I like C for doing fast physics calculations but it sucks having to reinvent the wheel all the time.
As someone who has worked primarily in C/C++ and assembly, I actually really enjoy C strings. It's the most natural from the perspective of the computer and while (*s) is effective enough for most string parsing. I'm not saying they're perfect, and there are times where I just want the string split methods that python has, but I personally would not want to see any changes. As for the array size, just pass it as another argument. Maybe I haven't worked on large enough projects to be affected by that sort of thing though.
@@alexanderlea2293there's str, which is a slice of UTF-8 in memory somewhere and String, which is the mutable, resizable string. Pretty uncontroversial if you're ok with memory slices and Vec both existing. Are you counting all the pointer types like Box, Rc etc you can wrap str in? Or are you talking about OsStr, Path, CStr and all the other weird interop types? They're occasionally useful, but not a huge deal. Compare with the million custom string types people create for "performance" in C++...
@@SimonBuchanNz I was talking about the catering and osstr equivalents. It's a bit crazy to have a language which requires more than one type of string to use fundamental programming tools.
walter bright sounds like a genuinely smart computer engineer. I hope he does not get along with his ex student jesse fuschiaman to create a cybercrime empire specializing in blue screens of death.
If you love C, you will love Odin. Odin has that idea proposed in this article, in fact it offers a bit more than that in an elegant way. Odin also has map type and a string type, and more (types). Odin keeps the simplicity of C but fastforwards to the context of modern day computer architecture. When I write C code I always feel like I'm in danger. When I switch to odin I feel free, and I don't have to jam all types of abstractions into my head (*ahem* C++, rust).
I dunno, is it really that bad? maybe it's because I'm used to it, so everytime I use arrays in C I tend to make then dynamic, so I always have a variable with it's capacity along side it or if I only need one array at a specific place I will make it one index longer and put a "terminator" so I generally don't trip myself up.
In my experience with C++ 90% of the time the array size is known at compile time so I just use references to C-style arrays (typedef'd to avoid the ugly syntax). And when the size is dynamic, most of the time I need to allocate it on the heap anyways so I might as well use std::vector or std::string. All that's left are the relatively rare cases that Rust slices solve.
It was maybe an error but this concept is not far from Rust &str or span, readliy adopted to other langugages for speed and less allocation. Surely in safer ways but cocnept is the same. Even in C++ they added it again in safer form
Why not implement the fat pointer in overloaded malloc and free functions dedicated to arrays? In theory, the new malloc implementation could always allocate a block of memory the size required for the array but include a size_t worth of bytes before the first element in the array. It would then just return the pointer to the first array element. These pointers would be fully backward compatible with all C code since the beginning of time, but if you index from the pointer in the negative direction you will be able to access the size_t that contains the size in bytes. Since C isn't object oriented the syntax for accessing this new size information would always have been done through a new library function anyway... something like size_t length = myarr.getSize(); doesn't fit in the design of C.
The simplest way I think, is to use a composite type, with typedef, you create a struct, that contains a void* data, a size_t size, and an enum for the type, that is declared with the type width, I use this a lot you just typedef it into a nice array_t, or t_array and boom you have a simple and easy to use array. you can even go a step further, and use generic functions that are only facade to fonctions pointers switch statements to redirect your array to the correct type specific functions, based on the value of your enum. ( and in some cases if the compiler is able to see that your functions are only there to redirect your array to another functions, the compiler might replace your switch statement by a look up table reducing a bit the overhead).
@@pierreollivier1 Yes, I think I see what you mean, and that is very much like what I proposed. This is due to the way C plays fast and loose with memory. Types in C are really just a means for calculating address offsets. Under the hood the languge doesn't really treat structs any differently than arrays... both end up doing just raw pointer math to get to what (you hope) is the desired data. The problem I see with the approach you propose is that having the void* first in your struct means that the size is dangling out at the end of the struct. In that case you actually have to do two pointer indirections to access your real data, first you have to dereference the struct to get to the void* that holds your data, then you have to compute the offset from that array head to get to the element you want. In my case there's only ever one pointer involved, the pointer to the first element in your array just like all C arrays. But in my case I leverage the fact that 99.9% of developers only ever do positive pointer offsets to sneak the extra size data in at a known offset from the pointer. That offset just happens to be negative.
@@DJCloudPirate I understand your approach, and yes, my method is clearly a very simple one. I believe many people have used this approach at some point. However, my main point is not about the implementation details, but rather about the fact that the language already provides a way (though not optimal) to handle bounded arrays. I don't think adding more to the language would be very useful in practice. It's one of those situations where people are accustomed to the way it's done in C and may not necessarily benefit from that feature even if it were implemented. Nevertheless, I find it interesting as it reminds me of how the system keeps the heap from becoming too fragmented by adding some embedded data at each allocation.
Yeah, I ended up implementing smart arrays in my C codebase. It was not a big deal, though. The only thing I'm missing from C is smarter function macros. I'm tired of the "\" line termination and the difficulty debugging them.
C offers structs to pair arrays with their length. it takes minimal effort, so i don't consider it a mistake. it being a systems programming language, it would be worse if you were forced to always have the length stored
I always define my own "fat pointers" when I program in C. It's not just great for reducing bugs, you also get to create views/slices of larger arrays so you rarely need to allocate anything. Super nice pattern.
I've been dealing with the Wasmer C api recently. The way they pass arrays is with a struct whatever_vec_t { size_t size; whatever* data; } It's not a bad idea, and intrinsic language support would be awesome. However, if you can use a C++ compiler to compile your mostly C-like code, you can use some small footprint templates to help with that. To get the size of a static array, template void foo( const MyType (&array)[N]) { c_function( FatPointer{array, N}); } I agree that being able to make sub-strings and slices without any memory allocation is great. std::string_view and std::span are staples in my code. I basically use span as the fat pointer type, as I'm sure it was intended.
I found that to be a fantastic feature in a physics simulation I've been working on. Minimizing the need for reallocations, instead keeping track of where the "meaningful data" is currently stored in a large buffer, does wonders for the performance. In a lot of situations it's easiest to have arrays keep track of their own size, but the fact that C doesn't do that encourages you to rethink how you handle your data and sometimes it gives you some chances to optimize for speed. As a physics major, I really appreciate C for that.
If you use all the static analysis and instrumentation your compiler provides, then C is a much safer and more usable language. Without doing that, pretty much any C program over 10k lines will have some nasty undefined behaviour lurking somewhere.
C is the best language in terms of its power and speed. If you look from a customers perspective, c is always the best language for any job. The problem is the limited system library and general unsafe pitfalls developers who don’t know better (and developers who do) fall into.
@@KayOScode " If you look from a customers perspective, c is always the best language for any job. " ??? C is the wrong choice for just about any job. The number of footguns is unparalleled outside of the esolang space.
still not sure if enough people talk about the absolute beauty of a feature that C allows, that is an array pointer. if you really want to pass an array of the fixed length in the most proper way (which is, without losing it's size upon conversion to pointer), you can just pass the array pointer to the function. then, for the sake of normal syntax, you can extract the actual pointer and size of the array into different variables inside the function. only real problem with this is that you cannot pass an array of variable size, meaning you can only use it to pass arrays of certain size N across the entire program (and the size is embedded inside the array pointer).
technically char a[] isn't exactly equivalent to char *a since if the length of the array is known at compile time, and not through malloc at run time, you can get the correct length with sizeof() ... but yeah, they are really similar.
They are the same, though you can't change the address of a[0]. But just talking about treating them like arrays, they are the same. You're just allocating the block of bytes before run time not during; you must still keep track of the size. sizeof gets the amount of bytes during compiling because different compilers have different widths of bytes for the c's data types: unsigned char *a; a = (unsigned char*) malloc ( ARRAY_SIZE * sizeof(unsigned char) ); going with what you said, I think structs make more sense in that context: typedef struct { unsigned int data1; unsigned int data2; unsigned long data3; } myCustomDataType; myCustomDataType twoIntsAndALong = (myCustomDataType*) malloc( sizeof(myCustomDataType) ); if I explisedly said I wanted 8 bytes to be allocated, then it makes the code way less portable if a int or long for another compiler is not the same amount of width of bytes. Makes it more tedious too.
The whole "functions and arrays decay to pointers at the slightest provocation" deal is also one of the most annoying features C++ inherited from C and a big reason why std::array exists. Stuff like this is why I struggle to take opinions like "Oh C is so simple you should use C" seriously.
I’m really used to the void *buf, size_t size calling convention used to work around this problem, but as soon as this article pointed out this was a problem I was on board. This may not be the source of most bugs for us but it is such a huge tedious time sink to constantly need to pass the size of an array or create verbose sizeof() constructions just to deal with the problem that the size of the array is not part of the type. This is such a precise identification of a failure of C and its refreshing amongst all the bitching about modern language features.
The array as a backing data structure of a string is incidental, not core to the identity of a string. Technically it could be implemented under the hood with a linked list or a tree or other more exotic things (I know this would suck in 99.9% of cases, just pointing out that strings with another backing data structure are still strings)
I think it's fine to pronounce char as burnt because a u8 char has almost nothing to do with an actual language characters. Coincidentally it has the latin alphabet plus some other random symbols because ASCII standard, but so much of the ASCII standard isn't language characters but things like tabs, spaces, newlines, return carriage, etc. Those aren't characters! They're dark and hidden in the ASCII standard... dark as burnt charcoal.
I was top one thing i envied C programmers - this mistake - in 1990 era Pascal... when i was young, brave and stupid ... now i'm only experienced stupid ;)
Assembly is code and it is data! It’s called self modifying code. I love that trick. And literally yesterday a kid asked me why to learn C. And literally my words were: “you really know how arrays works because they are not trivial to pass around.”
@@stefanalecu9532 assembly language but in forth you could do the same thing since it’s a command interpreted language in which you define words. So you can generate those words based on situations and also do self modifying code. Although I hardly coded in forth. But in assembly 6502, 68000 and x86 I used it a lot.
7:14, the only "unsafe design" about those is that, when the vector changes its size, it's not granted to stay at the same location in memory, so the iterators keep pointing to old address. 1 just needs to "refresh" them. But this need exists only 1 time per allocation (size changing). This is not made automatically due to possible performance. It's like web pages that are always refreshing themselves vs those waiting for the user to do it: the 1st is more comfortable, but wastes performance/resources from the machine. The operator [] hasn't this issue, because it comes all the way from the beginning. But has a performance penalty. I personally use iterators intensively. I only had this issue once. 8:55, agree. This is awkward because, for every 1 of the millions of f()s, the code will has this amount of lines. The way I use to do this is to write a macro only 1 time, calling it everywhere: #if NEWC #define arrpass(type, name, dim) type name[..] #elif C99 #define arrpass(type, name, dim) type name[dim] #else #define arrpass(type, name, dim) type *name #endif Then f()s will be written like (doesn't matter the standard): extern void foo (const size_t dim, arrpass (char, a, dim));
Re: arrays and pointers, I am pretty sure this was changed somewhat in the years since this was actually written. You can specify that a pointer argument can only have length 1 (not an array), must be not-null, that the array arg must also have an arg that specifies the length, and a few other issues. Compiler support for checking this statically, is hit or miss still. But it's increasingly decent. The string thing is horrible though. I think the way that Plan 9 handled UTF was better than any other C-library I've seen. But they aren't in widespread use and probably haven't been updated to the latest unicode standards.
C's biggest mistake is relying on preprocessor. I actually love having a powerful preprocessor, but it should be a complimentary tool. In C it's used instead of proper modules and proper constants.
Alternatively you can just typedef a simple struct to achieve the same improvement without needing to change the language. typedef struct my_array_t { size_t dim; char * data; } my_array_t; void foo(my_array_t a);
this from richard mann is a concept of a helmet hacker this worst vice that the thieves have is a much worse vice than the drugs themselves and in addition to the money it is cursed to be remotely attacking other users with those intentions that this was done predators concept
holy shit someone finally said it, god i hate this. i'm too scared to use ever use raw arrays in c++ because it is so easy to shoot yourself in the foot with them
lmao I wouldn't call "hasklul" the pinnacle of programming languages. idris for example is more powerful than haskell and more high level. APL is another one that's difficult but extremely powerful in a different way. i have no experience with lisp but apparently you can do crazy meta stuff with it. i do agree that rust is a step below haskell though. note: i'm using the blub scale here (blub programmer scale)
Suffices to say C does not have a built-in type "array". All it has is quite convenient syntactic sugar for operating with contiguously allocated data and the respective conceptual wrapper. That's it. If one, indeed, needs an "array", say as in linear algebra, in C, it must be properly implemented. In contrast, Fortran and Pascal (to name close languages that are still in use) had the array type before C, which makes all references to weak contemporary hardware irrelevant. So, decaying arrays to a pointer was a conscious decision. The fact that so much can be done with so little is almost a miracle and reflects how deep was that decision.
This would require that &array[index] returns an array rather than a pointer, and pointers and arrays would no longer be compatible - big fat No thank you. Also strings don't just need NUL termination because the array size is unknown, it needs it because the array may be larger than the string.
This video is talking about C. C# is a very different animal. That's cool though, I've written a few Space Engineers scripts myself. Good motivation to learn how to code, kudos for the effort. ;)
Yeah, and at the same time it produces binaries that are smaller than C binaries (using musl). Hello world in zig is just 2kb. It's a sharable static binary (no runtime dependencies) on linux.
It's not just `Zig` that have solutions for this problem. It's basically all programming languages fixed this problem. Except maybe some older ones. All fix it differently and they also added a big pile of features you never asked for.
cant you just typedef a struct with a pointer and a size_t? Or... pass in the length of the array as the second argument? I dont understand how this is "the fundamental mistake" of C.
@@Ryan-in3ot None, because you can't pass arrays with C. Functions that taken pointers meant to represent arrays though? Lots of them. scanf, strlen, strcpy, qsort, bsearch...
It's weird that C functions are aware of the size of lower dimensions of multi-dimensional arrays, but not the first dimension. Pass a 1D array to a function. The function knows the size of the objects, but the array "decays" to a pointer. Like having a string of beads, but it doesn't know how long the string is. Pass a 2D array. The array decays to a pointer to the first sub-array. It knows the size of that array. Like a spreadsheet, where the function knows the size of each row and cell entry, but has no idea how many rows there are. Pass a 3D array. Now it's a pointer to a 2D array of defined size. Like a multi-layer cake - the function knows the sizes of each layer, but no idea how many layers there are. Why is the first dimension the exception?
What do you mean you are coming around to Go? I thought you always liked it just for a couple things like naked returns and you can't tell everyone that you are a Rust developer.
It took me a while but I really warmed up to python3's treatment of strings. Strings really aren't arrays they are the idea of textual data. That textual data can be encoded in different ways and when you encode a string it becomes an array. Of course the string has to be stored in memory and just like everything else it is a block of memory, but it conceptually is not an array.
but what does this get you? its conceptually appealing, but i don't know that i could actually generate a description of that conception other than the ole mathematicians fallacy of platonic beauty.
@@homelessrobot It is similar to any other guard rail. It forces you to think about encoding and explicitly convert between the idea of text and a buffer of memory filled with encoded values. It prevents you from accidentally mixing the manipulation of text and the manipulation of an array in a way that could mess things up. It also holds your hand and makes unicode work.
It's weird i also many times feel like writing C, but then end up feeling like shit 😂 I think its cause every time i think how something is in memory i think it as a c struct, so i unintentionally think in C
You are supposed to know how much data is in something you have declared. Pcs be like beep boop, they don't know what you want or how to do it. you tell them how to do it with what they can do.
4:09 nooo! It’s gif as in gill… and mut as in nut and char as in charcoal… everyone one knows that… any other pronunciation is just people taking the piss
Coming from an embedded background (MCUs, MPUs), it's easier to visualize memory alignment using the existing implementation. I don't want any implicit bytes being used by a language feature. And what should the fatpointer's implicit 'size_t' be for 8-bit/16-bit/32-bit systems? In a system with 256 bytes of RAM(for e.g. ATtiny88), there's no need for a 32-bit size_t even if it's a uint32_t array. Standardizing this seems more of a nightmare.
I don't really get all the null hate. Having to contain an extra element with *every* pointer just seems wasteful when you can give up a single sentinel value within the pointer type. What, you actually need the zero page for data? (Sure, we could make use of the unused bits since pointers aren't actually the full 64 bits wide, but that's a lesson in "yet"). At best wrap a macro (probably as a language builtin) around the null check. Kotlin, for example, has safe calls like `val x = myArr?.length ?: -1;` - Is this the fix people rant about not having in languages like C?
You don’t need to have extra information with every pointer. The pointer being 0 represents the empty variant in an option type so you can just use the unsigned word size representation
Weather the programming language has an Optional, Maybe, Type? etc. doesn't matter. What matters is that the programming language either prevents NullPointerExceptions completely by e.g. wrapping the value in another object and having Null-Safe methods or, if added to the language retroactively, warnings and linting to alert the programmer to the danger. These warning should not come from programmers comments which are unreliable, but from the language itself.
@@Jmcgee1125 In Rust, references are never null. The generic Option enum (Rust's enums essentially being C's tagged unions, but much nicer) has two variants: Some(T), and None. The None variant is the equivalent of a null pointer, so if a function could return a null reference to a T, it'll instead return `Option` instead of `&T`. `Option.unwrap()` is the rough equivalent of forcibly deferencing - the program will crash if it's a None; Option.unwrap_unchecked() will give you the inner value without doing any checks whatsoever, making it be undefined behavior to call if the Option is the None variant. Methods like `Option.is_none()` exist, though normal usage of Options is `if let Some(foo) = fn_that_returns_an_option() { bar(foo); } else { baz(); }`. Options are part of libcore (as opposed to libstd or a 3rd party library) - even the most barebones embedded systems setup will have Options - and are used extremely extensively - they might very well be the type with the single highest usage through the entire language. Types like NonZeroU32 exist (which are just a u32/uint32_t that cannot be 0 (making one equal to 0 is undefined behavior)) with the explicit optimization that an Option is the same size as a NonZeroU32. The Rust compiler is extremely free with the optimizations it'll make (and that's half the point of all Rust's rules - the compiler can optimize in ways that other languages can only dream of), but this is an example of an explicit one you can depend on it doing.
I think the point is that if you require programmers to pass a length and a pointer as two separate arguments then they can get out of sync by accident (programmer's mistake), whereas here the suggestion is to have an array type that inherently knows its own length, which gets communicated automatically when passed as an argument. This is basically what a slice (`&[T]`) does in Rust.
@@itellyouforfree7238 That seems like it has equally as many dangers. How can this be done accurately? Maybe you can track a max length...maybe. But then you still would have to pass in how much of the array you have filled in, so you don't actually get a win. In fact, by saying it tracks the length and using that field, you will introduce way more bugs when programmers rely on the length that is wrong since is not actually how much of it is used. By having to explicitly pass the length, you are telling the programmer, hey you better keep track of this and tell me the right value which avoids a ton of bugs, not introduces them.
@@InfiniteQuest86 Think of it this way: "smart arrays" that own their buffer ought to have both a capacity and a length (how much is actually initialized and valid); "slices" that do not own their buffer may only have a length, since they are merely a view inside a buffer owned by someone else. At least, this is how `Vec` and `&[T]` work in Rust.
C was created by engineers who just wanted to get stuff done. After creating it, they used it to get stuff done.
Arguably this isn't C's "mistake", the issue is that C needed to be backwards compatible with B at the time, B didn't have arrays, it had "vectors" where it was just a pointer that pointed to a memory block. For B, arrays and pointers were the exact same. This is also why you can't just directly assign arrays in order to copy them. You _can_ however get both behaviors by wrapping arrays in a struct. Structs let you directly assign each other to copy and struct sizes are known at compile time.
Moral of the Story: Backwards compatibility can hurt you badly.
moral of the story is structs are the dongles of C
True words. One could even argue that the root of all evil (in programming) is (bad) legacy. Be that Be that BadThingsTM that you propagated into the future or GoodThingsTM that were left out of the future because of legacy.
One could also argue that the whole x86 both hw and sw is a clusterfsck of bad legacy, and the only good thing to do looking forward would be to design a new (proper) architecture and run legacy emulated/virtualized.
Pains me deeply having to admit that, for all it's faults, Apple was the only (major) player to consistently tell the eco to "put up or shut up". And lo and behold, they put up and moved onward/forward.
@@ErazerPT Objective-C could've been done much better. They royally messed it up.
computer scientists, putting the backward in backwards compatibility since the late 1960s
@@LeviShawando Obj-C is a language for NScrazy people
Walter Bright is a freaking LEGEND. He wrote the Zortech C/C++ compiler. The first such compiler for PCs in the 80s. He's the real deal.
Zortech was my first compiler. It came free with a C book my parents bought me in middle school. I hadn't even thought about that compiler in decades. Thanks for the shot of nostalgia!
@@DJCloudPirate I like to say that Walter Bright taught me C. I read every line of source code that came with that compiler and database supliment. I even printed out all the source files. It was just over a case of wide fanfold paper.
D lang also.
Also Empire game creator
I think a language being successful is when there is a lot of people who still use it 50+ years later.
It is fast, when I'm simulating orbits for a 100 years I don't want to wait 100 years for simulation to finish
@@realdragon And when I don't want to write code for 100 years, I use something else. Beeflang for example.
@@tempname8263 Sure, go use the thing you want or is the best for the job
I mainly use C for work. The number one thing I hate is that enums decay to the underlying type.
Is "enum class" different in this regard?
@@BboyKeny enum class is c++, but yeah enum class is generally typesafe in that you can't just treat it as the underlying type without a cast.
And yet we can't specify the underlying type (pre C23) - it's so insanely annoying
@@SVVV97 true. C++ fixes this by allowing for syntax like
enum MyEnum : char {A, B, C};
But it is kind of dumb that C does get updates here and there and yet it took so long to get custom sized enums.
@@SVVV97 yes, and the biggest issue is in fact that the underlying type could be int16_t, i.e. only values up to 2^15-1 are safe
I only have limited C experience but I dealt with this problem by assuming arrays just don't exist, which isn't strictly true, but is enough to eliminate confusion. From then on, if I wanted a proper array I would define my own struct.
I couldn't do much about null except programming very defensively, so that felt like a much more significant problem.
I love this style of reading RFC-like articles. Such a fresh air from the common JS dev blogpost reactions.
As a game developer, this is the first Primeagen video I understand fully from start to finish
As a web developer, I hate web development.
As a neurological nanotechnologist, I shid and cummed 💀
As an electrical engineer, same (mostly)
lol
3:30 you can use a pointer to an array and its expressed as int (*foo)[N]. In fact, you can use the VLA syntax to do this:
void add(size_t size, int (*foo)[size]);
It's time to create the C** programming language, with array contents that start at A[1] and A[0] that contain the array dimention, to give new programmer more ways to fuck their code and segfault because of an unintentional change in A[0]. To make things more complicated, if A[0] does not contain enough space to store the entire lenght of the array, the compiler can randomly choose between transforming A[0] to a pointer to a bigger container or expand A[0] with A[1] without noticing, and it should be inpossible to know which of the 2 happened until a segfault happens
final boss: void** programming language where type annotations don’t exist at all, but if you get them wrong you brick your cpu
Somebody call the cops on this man
UNIX lredy hs tht covered.
You should really have pointed out who Walter Bright is here -- he's the guy who created the Zortech C++ compiler. I also want to say. he wrote Turbo Linker.
The creator of D programming language
and the zortech c++ compiler was/is the first commercial c++ compiler for Windows, and maybe the first c++ compiler ever if you don't think transpilation to c counts.
Shoutout to Mr. Bartosz Milewski! A great academic/programmer renowned for teaching category theory to programmers.
Definitely check him out if you're interested :)
Odin has effectively this exact feature, and even uses it with the old array syntax (T[], but you have to pass the array as f(x[:]) to give the length too, see "slice")
Yeah I use Odin for personal stuff now, but definitely cannot for work.
Haskell is so good that it is even a brand for hair products.
delete this
Lol
i agree with this video, null is annoying but i feel like its mangeable as long as you understand why it exists
I think null is a bigger problem when you venture outside of C; in C, yeah, it causes some bugs, but it's a fundamental part of how things work. In Java, for example, a fallible function is not just unidentifiable from its type signature, but also failure can be either by raising an exception or returning NULL or whatever, and since objects are implicitly references, there's less clarity about which things can even potentially be NULL vs which things are safe, etc.
In C, errors are done through error code returns and writing output to an argument pointer or specific value returns, and you can tell which it could potentially be from the declaration, so you have to deal with much less.
I agree, if you know about null you can totally control it, but requires to build good habits, and people have zero idea about how habits are made or destroyed.
@@laughingvampire7555 Its the age old "This language is great because you have to learn to be disciplined to use it" vs "If a language requires you to be disciplined to use it, it isn't great".
@@PoorlyMadeSweater every professionalist has to have some sort of discipline. Would you like to have undisciplined construction workers building your house? IOr undisciplined car engineer designing you car? I wouldn't. In many professions you have to be disciplined otherwise you are risking your or somebody else's life. Only in computer programming people think that language and compiler has to save them from every mistake.
@@fr0zi Yeap, thats one side of the argument. The other is the human brain isn't as capable as people like to think. Trying to entertain every possible mistake you could be making while maintaining a mental image of the entire system is impossible. Every mistake the compiler cant prevent is a land mine every pro is just one hour of missed sleep away from making.
To use your metaphor, a *safe* language would require a buzz saw to contain a SawStop, a device that stops the blade when flesh is detected. A disciplined wood worker wouldn't argue against such a device simply because you *should* be good enough to not need one.
To me the biggest issue with C is the compilation process, the way the headers combine (and need to be manually created and maintained) and have name and macro collisions, additionally complicated thanks to lack of namespaces. Then everything else like:
- switch requiring break (inherited by many other languages unfortunately)
- null terminated strings
- no array length..
- Edit: variables uninitialized by default
- implicit casts, not just pointers and arrays
- lack of bools for conditions like if (int) being legal
- shadowing (very easy to have nested for loops with same "i")
..etc..
#define when break; case
You're welcome. Yes, this works. It's legal to put a break statement between the switch and the first case label.
Honestly yes, keeping headers up to date is an annoying extra step that is easy to forget. I've run into some pretty cryptic bugs when changing the type of an argument from int to size_t or something because I forgot to update the function prototype in the header. The lack of namespaces is also very annoying and practically discourages you from relying on libraries lest they clash with each other. Maybe that's why C kinda has the reputation of "you need to build all your tools from scratch". I like C for doing fast physics calculations but it sucks having to reinvent the wheel all the time.
@@Sealedaway You can get away with using one external library at a time after somehow forcing your editor to find it. LOL.
As someone who has worked primarily in C/C++ and assembly, I actually really enjoy C strings. It's the most natural from the perspective of the computer and while (*s) is effective enough for most string parsing. I'm not saying they're perfect, and there are times where I just want the string split methods that python has, but I personally would not want to see any changes. As for the array size, just pass it as another argument. Maybe I haven't worked on large enough projects to be affected by that sort of thing though.
And I'll take 1 C String over the 6 "Rust" Strings any day of the week.
@@alexanderlea2293same
@@alexanderlea2293there's str, which is a slice of UTF-8 in memory somewhere and String, which is the mutable, resizable string. Pretty uncontroversial if you're ok with memory slices and Vec both existing.
Are you counting all the pointer types like Box, Rc etc you can wrap str in? Or are you talking about OsStr, Path, CStr and all the other weird interop types? They're occasionally useful, but not a huge deal.
Compare with the million custom string types people create for "performance" in C++...
I have worked in a very large c++ project, and it was awful. The project started in the 80s though, so it definitely used some old patterns
@@SimonBuchanNz I was talking about the catering and osstr equivalents. It's a bit crazy to have a language which requires more than one type of string to use fundamental programming tools.
walter bright sounds like a genuinely smart computer engineer. I hope he does not get along with his ex student jesse fuschiaman to create a cybercrime empire specializing in blue screens of death.
Thanks for putting the article in the description !
If you love C, you will love Odin. Odin has that idea proposed in this article, in fact it offers a bit more than that in an elegant way. Odin also has map type and a string type, and more (types). Odin keeps the simplicity of C but fastforwards to the context of modern day computer architecture.
When I write C code I always feel like I'm in danger. When I switch to odin I feel free, and I don't have to jam all types of abstractions into my head (*ahem* C++, rust).
Null termination is a pain yes. So make a struct with a pointer and a length? 🎉
I dunno, is it really that bad? maybe it's because I'm used to it, so everytime I use arrays in C I tend to make then dynamic, so I always have a variable with it's capacity along side it or if I only need one array at a specific place I will make it one index longer and put a "terminator" so I generally don't trip myself up.
As someone writing a simpler implementation of Monkeyscript in D, I completely stand by this article.
I'd pick switch-case requiring break.
So many bugs have been caused by forgetting break.
Add an explicit fall-through keyword instead.
#define when break; case
@@JuusoAlasuutari that's quite clever!
In a "disgusting hack that's required to work around a poor design decision" kind of way.
@@sparky173j as is tradition in C ;)
there are already -Wimplicit-fallthrough and attribute__((fallthrough))
In my experience with C++ 90% of the time the array size is known at compile time so I just use references to C-style arrays (typedef'd to avoid the ugly syntax). And when the size is dynamic, most of the time I need to allocate it on the heap anyways so I might as well use std::vector or std::string. All that's left are the relatively rare cases that Rust slices solve.
It was maybe an error but this concept is not far from Rust &str or span, readliy adopted to other langugages for speed and less allocation. Surely in safer ways but cocnept is the same.
Even in C++ they added it again in safer form
Why not implement the fat pointer in overloaded malloc and free functions dedicated to arrays? In theory, the new malloc implementation could always allocate a block of memory the size required for the array but include a size_t worth of bytes before the first element in the array. It would then just return the pointer to the first array element. These pointers would be fully backward compatible with all C code since the beginning of time, but if you index from the pointer in the negative direction you will be able to access the size_t that contains the size in bytes. Since C isn't object oriented the syntax for accessing this new size information would always have been done through a new library function anyway... something like size_t length = myarr.getSize(); doesn't fit in the design of C.
The simplest way I think, is to use a composite type, with typedef, you create a struct, that contains a void* data, a size_t size, and an enum for the type, that is declared with the type width, I use this a lot you just typedef it into a nice array_t, or t_array and boom you have a simple and easy to use array. you can even go a step further, and use generic functions that are only facade to fonctions pointers switch statements to redirect your array to the correct type specific functions, based on the value of your enum. ( and in some cases if the compiler is able to see that your functions are only there to redirect your array to another functions, the compiler might replace your switch statement by a look up table reducing a bit the overhead).
@@pierreollivier1 Yes, I think I see what you mean, and that is very much like what I proposed. This is due to the way C plays fast and loose with memory. Types in C are really just a means for calculating address offsets. Under the hood the languge doesn't really treat structs any differently than arrays... both end up doing just raw pointer math to get to what (you hope) is the desired data. The problem I see with the approach you propose is that having the void* first in your struct means that the size is dangling out at the end of the struct. In that case you actually have to do two pointer indirections to access your real data, first you have to dereference the struct to get to the void* that holds your data, then you have to compute the offset from that array head to get to the element you want. In my case there's only ever one pointer involved, the pointer to the first element in your array just like all C arrays. But in my case I leverage the fact that 99.9% of developers only ever do positive pointer offsets to sneak the extra size data in at a known offset from the pointer. That offset just happens to be negative.
@@DJCloudPirate I understand your approach, and yes, my method is clearly a very simple one. I believe many people have used this approach at some point. However, my main point is not about the implementation details, but rather about the fact that the language already provides a way (though not optimal) to handle bounded arrays. I don't think adding more to the language would be very useful in practice. It's one of those situations where people are accustomed to the way it's done in C and may not necessarily benefit from that feature even if it were implemented. Nevertheless, I find it interesting as it reminds me of how the system keeps the heap from becoming too fragmented by adding some embedded data at each allocation.
Yeah, I ended up implementing smart arrays in my C codebase. It was not a big deal, though. The only thing I'm missing from C is smarter function macros. I'm tired of the "\" line termination and the difficulty debugging them.
C offers structs to pair arrays with their length. it takes minimal effort, so i don't consider it a mistake. it being a systems programming language, it would be worse if you were forced to always have the length stored
I always define my own "fat pointers" when I program in C. It's not just great for reducing bugs, you also get to create views/slices of larger arrays so you rarely need to allocate anything. Super nice pattern.
can you please continue to cook?
I've been dealing with the Wasmer C api recently. The way they pass arrays is with a struct whatever_vec_t { size_t size; whatever* data; } It's not a bad idea, and intrinsic language support would be awesome.
However, if you can use a C++ compiler to compile your mostly C-like code, you can use some small footprint templates to help with that. To get the size of a static array,
template void foo( const MyType (&array)[N]) { c_function( FatPointer{array, N}); }
I agree that being able to make sub-strings and slices without any memory allocation is great. std::string_view and std::span are staples in my code. I basically use span as the fat pointer type, as I'm sure it was intended.
I found that to be a fantastic feature in a physics simulation I've been working on. Minimizing the need for reallocations, instead keeping track of where the "meaningful data" is currently stored in a large buffer, does wonders for the performance. In a lot of situations it's easiest to have arrays keep track of their own size, but the fact that C doesn't do that encourages you to rethink how you handle your data and sometimes it gives you some chances to optimize for speed. As a physics major, I really appreciate C for that.
If you use all the static analysis and instrumentation your compiler provides, then C is a much safer and more usable language. Without doing that, pretty much any C program over 10k lines will have some nasty undefined behaviour lurking somewhere.
But it won't save you from logic bugs which is even worse
I like the simplicity where an array with five elements is exactly five times the size of each element. Use c++ if you want a custom array type
Another solution is to use bounds checking in the compiler options. The code runs more slowly but it is good for testing
C is the best language in terms of its power and speed. If you look from a customers perspective, c is always the best language for any job. The problem is the limited system library and general unsafe pitfalls developers who don’t know better (and developers who do) fall into.
@@KayOScode " If you look from a customers perspective, c is always the best language for any job. "
??? C is the wrong choice for just about any job. The number of footguns is unparalleled outside of the esolang space.
"I like the simplicity where an array with five elements is exactly five times the size of each element."
std::array
@@isodoubIet never thought id dislike someone's comment and then like the next one lmao
you code in C, I code in C, I'm just built different, so are you
still not sure if enough people talk about the absolute beauty of a feature that C allows, that is an array pointer. if you really want to pass an array of the fixed length in the most proper way (which is, without losing it's size upon conversion to pointer), you can just pass the array pointer to the function. then, for the sake of normal syntax, you can extract the actual pointer and size of the array into different variables inside the function. only real problem with this is that you cannot pass an array of variable size, meaning you can only use it to pass arrays of certain size N across the entire program (and the size is embedded inside the array pointer).
technically
char a[]
isn't exactly equivalent to
char *a
since if the length of the array is known at compile time, and not through malloc at run time, you can get the correct length with sizeof()
... but yeah, they are really similar.
They are the same, though you can't change the address of a[0]. But just talking about treating them like arrays, they are the same. You're just allocating the block of bytes before run time not during; you must still keep track of the size. sizeof gets the amount of bytes during compiling because different compilers have different widths of bytes for the c's data types:
unsigned char *a;
a = (unsigned char*) malloc ( ARRAY_SIZE * sizeof(unsigned char) );
going with what you said, I think structs make more sense in that context:
typedef struct {
unsigned int data1;
unsigned int data2;
unsigned long data3;
} myCustomDataType;
myCustomDataType twoIntsAndALong = (myCustomDataType*) malloc( sizeof(myCustomDataType) );
if I explisedly said I wanted 8 bytes to be allocated, then it makes the code way less portable if a int or long for another compiler is not the same amount of width of bytes. Makes it more tedious too.
The whole "functions and arrays decay to pointers at the slightest provocation" deal is also one of the most annoying features C++ inherited from C and a big reason why std::array exists. Stuff like this is why I struggle to take opinions like "Oh C is so simple you should use C" seriously.
C is like the nunchuks of programming. Its so simple, just two sticks and a piece of string, what could go wrong?
@@PoorlyMadeSweater I used to practice with those and, ironically, they're a lot safer than C.
Isn’t the array decay problem fixed by passing an array pointer to a function instead of passing the array directly?
I’m really used to the void *buf, size_t size calling convention used to work around this problem, but as soon as this article pointed out this was a problem I was on board. This may not be the source of most bugs for us but it is such a huge tedious time sink to constantly need to pass the size of an array or create verbose sizeof() constructions just to deal with the problem that the size of the array is not part of the type. This is such a precise identification of a failure of C and its refreshing amongst all the bitching about modern language features.
We need C-- lang.
a[-1] = size and a[0] = first element 😂
Except for the occasional screeching, I'm really enjoying this channel on my headphones.
You probably pronounce it like that because "char" is already a word (to burn something so that it chars), but "mut" isn't.
The array as a backing data structure of a string is incidental, not core to the identity of a string. Technically it could be implemented under the hood with a linked list or a tree or other more exotic things (I know this would suck in 99.9% of cases, just pointing out that strings with another backing data structure are still strings)
Learned haskell just so I could put the sticker on my laptop and work at a coffee shop (in Python)
I think it's fine to pronounce char as burnt because a u8 char has almost nothing to do with an actual language characters. Coincidentally it has the latin alphabet plus some other random symbols because ASCII standard, but so much of the ASCII standard isn't language characters but things like tabs, spaces, newlines, return carriage, etc. Those aren't characters! They're dark and hidden in the ASCII standard... dark as burnt charcoal.
I was top one thing i envied C programmers - this mistake - in 1990 era Pascal... when i was young, brave and stupid ... now i'm only experienced stupid ;)
Assembly is code and it is data! It’s called self modifying code. I love that trick. And literally yesterday a kid asked me why to learn C. And literally my words were: “you really know how arrays works because they are not trivial to pass around.”
So low-level Lisp/Forth?
@@stefanalecu9532 assembly language but in forth you could do the same thing since it’s a command interpreted language in which you define words. So you can generate those words based on situations and also do self modifying code.
Although I hardly coded in forth. But in assembly 6502, 68000 and x86 I used it a lot.
Can’t you just make a struct with a pointer and a size component?
7:14, the only "unsafe design" about those is that, when the vector changes its size, it's not granted to stay at the same location in memory, so the iterators keep pointing to old address. 1 just needs to "refresh" them. But this need exists only 1 time per allocation (size changing). This is not made automatically due to possible performance. It's like web pages that are always refreshing themselves vs those waiting for the user to do it: the 1st is more comfortable, but wastes performance/resources from the machine.
The operator [] hasn't this issue, because it comes all the way from the beginning. But has a performance penalty. I personally use iterators intensively. I only had this issue once.
8:55, agree. This is awkward because, for every 1 of the millions of f()s, the code will has this amount of lines. The way I use to do this is to write a macro only 1 time, calling it everywhere:
#if NEWC
#define arrpass(type, name, dim) type name[..]
#elif C99
#define arrpass(type, name, dim) type name[dim]
#else
#define arrpass(type, name, dim) type *name
#endif
Then f()s will be written like (doesn't matter the standard):
extern void foo (const size_t dim, arrpass (char, a, dim));
someone in the chat said "you don't talk about java. You ashamed writing it."
Walter Bright is that prominent person, think volumetric Terry Davis.
Re: arrays and pointers, I am pretty sure this was changed somewhat in the years since this was actually written. You can specify that a pointer argument can only have length 1 (not an array), must be not-null, that the array arg must also have an arg that specifies the length, and a few other issues.
Compiler support for checking this statically, is hit or miss still. But it's increasingly decent.
The string thing is horrible though. I think the way that Plan 9 handled UTF was better than any other C-library I've seen. But they aren't in widespread use and probably haven't been updated to the latest unicode standards.
C's biggest mistake is relying on preprocessor. I actually love having a powerful preprocessor, but it should be a complimentary tool. In C it's used instead of proper modules and proper constants.
Alternatively you can just typedef a simple struct to achieve the same improvement without needing to change the language.
typedef struct my_array_t {
size_t dim;
char * data;
} my_array_t;
void foo(my_array_t a);
The only way to fix C is being serious about static analysis. Then it's not too bad anymore.
this from richard mann is a concept of a helmet hacker this worst vice that the thieves have is a much worse vice than the drugs themselves and in addition to the money it is cursed to be remotely attacking other users with those intentions that this was done predators concept
Haskell isn't the top of the type complexity hill. There are Idris, Agda, Coq, and other systems that have features the Haskell people wish they had.
I'm consistent with how I say these things. Char is char with an H. Mut is like the dog. Say it how it's spelled.
char *a is called a char-a-starred, or charizard for short. Duh.
holy shit someone finally said it, god i hate this. i'm too scared to use ever use raw arrays in c++ because it is so easy to shoot yourself in the foot with them
lmao I wouldn't call "hasklul" the pinnacle of programming languages. idris for example is more powerful than haskell and more high level. APL is another one that's difficult but extremely powerful in a different way. i have no experience with lisp but apparently you can do crazy meta stuff with it. i do agree that rust is a step below haskell though.
note: i'm using the blub scale here (blub programmer scale)
Suffices to say C does not have a built-in type "array". All it has is quite convenient syntactic sugar for operating with contiguously allocated data and the respective conceptual wrapper. That's it. If one, indeed, needs an "array", say as in linear algebra, in C, it must be properly implemented. In contrast, Fortran and Pascal (to name close languages that are still in use) had the array type before C, which makes all references to weak contemporary hardware irrelevant. So, decaying arrays to a pointer was a conscious decision. The fact that so much can be done with so little is almost a miracle and reflects how deep was that decision.
Seeing `#include void main() { int arr[] = {0, 42, 69}; printf("%d", 2[arr]);}` (newlines stripped) print 69 really showcases this shit to me.
@@MH_VOID The fact that array indexing is commutative will never cease to amuse me.
I would say the most successful programming language is the one used in the most projects.
what is it with the sagfaults
when i first startet c it took 2 weeks of writing programms to get my 1. sagfault (a +-1 bug)
Pernacious? Wasn't he a church father from Exaldrandia?
This would require that &array[index] returns an array rather than a pointer, and pointers and arrays would no longer be compatible - big fat No thank you. Also strings don't just need NUL termination because the array size is unknown, it needs it because the array may be larger than the string.
Rust developers are starting to sound like Python developers.
lol
Primagen say about Haskell programmers looking down on other ppl, but has he seen APL programmers
I mean, even though you can have something like c++ with cuda or rocm, writing basically c99 for the GPU with opencl feels kinda nice.
i use c and nim,they feels like js and react,perfect
the only reason I learned C# was because of the game Space Engineers and the desire to have automatic solar arrays and mining drones.
This video is talking about C. C# is a very different animal. That's cool though, I've written a few Space Engineers scripts myself. Good motivation to learn how to code, kudos for the effort. ;)
@@DFPercush oh, I just saw 'Cs' and was like "*.cs", I do like C. It's my home programming language.
So, basically Zig addresses all Cs biggest mistakes?
that's why zig born
Yeah, and at the same time it produces binaries that are smaller than C binaries (using musl). Hello world in zig is just 2kb. It's a sharable static binary (no runtime dependencies) on linux.
It's not just `Zig` that have solutions for this problem. It's basically all programming languages fixed this problem. Except maybe some older ones. All fix it differently and they also added a big pile of features you never asked for.
@@krux02irrelevant when only a few languages can be a drop-in replacement for C to do systems and embedded programming.
@@jakejakeboom
Nah, there are a big bunch of pile of languages that can do systems and embedded programming.
cant you just typedef a struct with a pointer and a size_t? Or... pass in the length of the array as the second argument? I dont understand how this is "the fundamental mistake" of C.
Technically you can but the endless list of buffer overflows means people dont do that.
Ok now make the standard library take that instead of arrays.
You can only go so far without environment support.
@@isodoubIet what functions in the c standard library even take in arrays?
@@isodoubIet if it is not string i don’t see where is issue wifh this approach
@@Ryan-in3ot None, because you can't pass arrays with C. Functions that taken pointers meant to represent arrays though? Lots of them. scanf, strlen, strcpy, qsort, bsearch...
char stands for charcoal which is left of you stool when you've done with c's segfaults
lack of fancy libraries and bad habbits of using them in other languages
this is how you should be lit in every video. 10/10 no notes.
if you aren't a specter of light reading code to me from the ethereal realm, then i don't want to hear it.
As a youtube only watcher. How do you dig up the old articles?
reddit.com/r/theprimeagenreact
It's weird that C functions are aware of the size of lower dimensions of multi-dimensional arrays, but not the first dimension.
Pass a 1D array to a function. The function knows the size of the objects, but the array "decays" to a pointer. Like having a string of beads, but it doesn't know how long the string is.
Pass a 2D array. The array decays to a pointer to the first sub-array. It knows the size of that array. Like a spreadsheet, where the function knows the size of each row and cell entry, but has no idea how many rows there are.
Pass a 3D array. Now it's a pointer to a 2D array of defined size. Like a multi-layer cake - the function knows the sizes of each layer, but no idea how many layers there are.
Why is the first dimension the exception?
Fun Fact: Bartosz Milewski is from Poland, so his name is pronounced more like Bartosh Milleffski.
Go lang was created in 2008.
What do you mean you are coming around to Go? I thought you always liked it just for a couple things like naked returns and you can't tell everyone that you are a Rust developer.
It took me a while but I really warmed up to python3's treatment of strings. Strings really aren't arrays they are the idea of textual data. That textual data can be encoded in different ways and when you encode a string it becomes an array. Of course the string has to be stored in memory and just like everything else it is a block of memory, but it conceptually is not an array.
but what does this get you? its conceptually appealing, but i don't know that i could actually generate a description of that conception other than the ole mathematicians fallacy of platonic beauty.
@@homelessrobot It is similar to any other guard rail. It forces you to think about encoding and explicitly convert between the idea of text and a buffer of memory filled with encoded values. It prevents you from accidentally mixing the manipulation of text and the manipulation of an array in a way that could mess things up. It also holds your hand and makes unicode work.
I really like V Lang. It is largely based off of Go but has built-in monads.
The V stands for Vaporware 😎
@@nighteule I've been using it with much success! There's some features that would be nice to have. But for a newer project it works quite well.
It's weird i also many times feel like writing C, but then end up feeling like shit 😂 I think its cause every time i think how something is in memory i think it as a c struct, so i unintentionally think in C
I think
i think the same things when i thought about thinking
_Cogito, ergo sum_
This poster *definitely* exists.
If gif is jif then git is jit and gift is jift?
big if true
giant will be jiant... my life is completely in shambles
You are supposed to know how much data is in something you have declared.
Pcs be like beep boop, they don't know what you want or how to do it. you tell them how to do it with what they can do.
holy shit its bartosz milewsky rearing his head bc you mentioned haskell and cpp in a video
The greatest mistake is makefile. It's the worst syntax ever written. Worse than php
C23-- the most recent version like just came out very recently lol, it is in fact very slow.
This makes no sense in so many different ways it's like a hypercube with all dimensions having a negative size
@@JuusoAlasuutari I'm talking about the update cycle my guy lol. Why would I be talking about literally anything else?
@@GiovanniCKC it was a little ambiguously worded
4:09 nooo! It’s gif as in gill… and mut as in nut and char as in charcoal… everyone one knows that… any other pronunciation is just people taking the piss
Coming from an embedded background (MCUs, MPUs), it's easier to visualize memory alignment using the existing implementation. I don't want any implicit bytes being used by a language feature. And what should the fatpointer's implicit 'size_t' be for 8-bit/16-bit/32-bit systems? In a system with 256 bytes of RAM(for e.g. ATtiny88), there's no need for a 32-bit size_t even if it's a uint32_t array. Standardizing this seems more of a nightmare.
Why no apostrophe in video title? i.e. C's
I don't really get all the null hate. Having to contain an extra element with *every* pointer just seems wasteful when you can give up a single sentinel value within the pointer type. What, you actually need the zero page for data? (Sure, we could make use of the unused bits since pointers aren't actually the full 64 bits wide, but that's a lesson in "yet"). At best wrap a macro (probably as a language builtin) around the null check. Kotlin, for example, has safe calls like `val x = myArr?.length ?: -1;` - Is this the fix people rant about not having in languages like C?
You don’t need to have extra information with every pointer. The pointer being 0 represents the empty variant in an option type so you can just use the unsigned word size representation
This is called the “null pointer optimization” in rust
@@32gigs96 So, a language builtin around the null check? I don't use Rust so I'm not familiar with how it does that sort of stuff.
Weather the programming language has an Optional, Maybe, Type? etc. doesn't matter.
What matters is that the programming language either prevents NullPointerExceptions completely by e.g. wrapping the value in another object and having Null-Safe methods or, if added to the language retroactively, warnings and linting to alert the programmer to the danger.
These warning should not come from programmers comments which are unreliable, but from the language itself.
@@Jmcgee1125 In Rust, references are never null. The generic Option enum (Rust's enums essentially being C's tagged unions, but much nicer) has two variants: Some(T), and None. The None variant is the equivalent of a null pointer, so if a function could return a null reference to a T, it'll instead return `Option` instead of `&T`. `Option.unwrap()` is the rough equivalent of forcibly deferencing - the program will crash if it's a None; Option.unwrap_unchecked() will give you the inner value without doing any checks whatsoever, making it be undefined behavior to call if the Option is the None variant. Methods like `Option.is_none()` exist, though normal usage of Options is `if let Some(foo) = fn_that_returns_an_option() { bar(foo); } else { baz(); }`. Options are part of libcore (as opposed to libstd or a 3rd party library) - even the most barebones embedded systems setup will have Options - and are used extremely extensively - they might very well be the type with the single highest usage through the entire language. Types like NonZeroU32 exist (which are just a u32/uint32_t that cannot be 0 (making one equal to 0 is undefined behavior)) with the explicit optimization that an Option is the same size as a NonZeroU32. The Rust compiler is extremely free with the optimizations it'll make (and that's half the point of all Rust's rules - the compiler can optimize in ways that other languages can only dream of), but this is an example of an explicit one you can depend on it doing.
4:04 "char" is pronounced the way it is specifically so it rhymes with "star" when pronouncing that oldest most fundamental type, char*.
I just pronounce "char" as "character"
Isn't Jai also fixing this?
just started watching. I dunno why, but i will like (it's like 99% case, 1% i just don't understand or/and don't care ) it i am sure. So i hit like.
Walter Bright created the D programming language
Lmao C++20 fixed this by giving us std::span which does the same thing but with more features coming along with it. I use it constantly in my code.
GIF is pronounced like graphic. Just like char is pronounced character
"If God himself told me GIF is pronounced "jif", I'd say 'Okay, Jod'."
@@MH_VOID *Zod
No, a foo(char a[..]) hides the dim parameter. In C, making things explicit is the goal.
Yeah I truly cannot understand what the complaint here is. It makes no sense. If you want a length, pass that in with the other argument. Wtf??
I think the point is that if you require programmers to pass a length and a pointer as two separate arguments then they can get out of sync by accident (programmer's mistake), whereas here the suggestion is to have an array type that inherently knows its own length, which gets communicated automatically when passed as an argument. This is basically what a slice (`&[T]`) does in Rust.
@@itellyouforfree7238 That seems like it has equally as many dangers. How can this be done accurately? Maybe you can track a max length...maybe. But then you still would have to pass in how much of the array you have filled in, so you don't actually get a win. In fact, by saying it tracks the length and using that field, you will introduce way more bugs when programmers rely on the length that is wrong since is not actually how much of it is used. By having to explicitly pass the length, you are telling the programmer, hey you better keep track of this and tell me the right value which avoids a ton of bugs, not introduces them.
@@InfiniteQuest86 You're conflating length and capacity.
@@InfiniteQuest86 Think of it this way: "smart arrays" that own their buffer ought to have both a capacity and a length (how much is actually initialized and valid); "slices" that do not own their buffer may only have a length, since they are merely a view inside a buffer owned by someone else. At least, this is how `Vec` and `&[T]` work in Rust.