8 ms·
How SerenityOS declares ssize_t
- rep_lodsb 4y ago<rant> "__SIZE_TYPE__" is a builtin symbol in the preprocessor, seriously? echo __SIZE_TYPE__ | cpp - # 1 "<stdin>" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "<stdin>" long unsigned int Okay, so it's defined in a file apparently. But why is that file automatically included in every program, and not the more usual type definitions? And is there really no better way to define "an integer the size of a pointer" than by going through this rat's nest of text substitutions? The C language seems almost designed on purpose to maximize uglyness. It is called "portable assembly language", but even in this day and age when there are only two or three relevant processor architectures, which have been largely designed around being able to run C code efficiently (to the detriment of everything else), it falls short of that. It is only portable at all because of include files that are included from other files, containing __MACROS__ calling on __OTHER__MACRO_S___ to the n-th degree. The most advanced compiler algorithms are then applied to the problem of transforming the resulting ((void *)(__pile_of(cr*p))()) back into machine code that is at least not completely terrible. Follow every obscure rule of the standard, and they might be so nice not to remove your carefully written null pointer check in the process! And people worship this uglyness and needless complexity, even as it strangles the life out of every other technology like a cancer that has been growing for 50 years. Professionals and hobbyists both, they celebrate how clever they are, being able to work around its deficiencies, think that they are dealing with the fundamentals of computer science rather than the grotesque evolution of an operating system originally written to typeset documents and play SpaceWar. Just once I'd like to see a new operating system - better yet, a new CPU! - that is not based on C and UNIX, one that is outright hostile to them at every level of abstraction. Not a single line of C code anywhere, different calling convention, different filesystem, user interface, networking etc.
- cultureswitch 4y agoWell my breakfast is on my feet now
- qalmakka 4y agoThat's cursed just the way I like it.
- st_goliath 4y agoTo expand further on that: POSIX only says that ssize_t needs to be able to store -1, i.e. the valid range is from -1 through 0 to some implementation defined maximum. (See the link in the article) A really cursed implementation could hack up compiler support for an asymmetric integer type that treats all-bits-set as -1 and everything else as a positive number, allowing ssize_t to hold all but the largest size_t values. While perfectly standards compliant, I guess this might break a bunch of implicit assumptions across various existing programs.
- riceart 4y agoThis isn’t particularly uncommon or without precedent. This is just treating SIZE_MAX as the sentinel value of an otherwise unsigned type. This is what is done for std::dynamic_extent in C++ for instance.
- im3w1l 4y agoThere's a really neat way to express asymmetric numbers that generalizes from both unsigned numbers and standard 2's complement signed numbers. It's easiest to explain in terms of how to interpret a particular bitpattern. As the first step, interpret the bitpattern as an unsigned int, u. If u <= T, for some threshold T, then we are done, the final value is u. If u > T, then we interpret it as the negative number u - UINT_MAX. T = UINT_MAX gives you the unsigned numbers, and T = INT_MAX gives you the two's complement. Because of modulus intense handwaving it's also easy to do addition, subtraction and multiplication using this representation.
- fanf2 4y agoThat isn’t allowed by the C standard: signed integer types have to be two’s complement, one’s complement, or sign-magnitude; they can’t be wildly asymmetrical about 0.
- deleted 4y ago
- NoZebra120vClip 4y agoThis seems to be about defining it, not merely declaring it. ("typedef" is short for "type definition", for example.)
- riceart 4y agoDefinition and declaration have specific meanings in C and C++. A typedef is part of a declaration. Declarations can be definitions but a typedef is not a definition in particular. https://en.cppreference.com/w/c/language/typedef https://en.cppreference.com/w/c/language/typedef
- dataflow 4y agoIs there any real-world system on which defining ssize_t to be ptrdiff_t would break actual programs that exist? (i.e. I'm not asking for hypotheticals here)
- addaon 4y agoYes -- anything that uses fat pointers and defines ptrdiff_t to be a fat pointer type that is larger than size_t/ssize_t.
- dataflow 4y agoWhat actual system is currently like this?
- addaon 4y agoI'm sure they're out there. The closest that comes to mind is the MSP430, but not quite -- although it has 20 bit pointers (with sizeof ptr being... 4, since they're padded to 4 bytes for storage) and has a 16 bit size_t, my recollection is that ptrdiff_t is also defined at 16 bits (which I think violates the C spec, which requires at least 17 bits?). I haven't worked with many other segmented architectures recently, but either there or capability machines are where I'd look.
- dataflow 4y agoThe entire purpose of my question was that "I'm sure they're out there" is as close as I've ever found an answer to this, so I was looking for an actual example, not a hypothetical one.
- addaon 4y agoI mean, a C spec compatible compiler for the MSP430 would be an example (see paragraph 7.20.3), TI has just decided to deviate from the language standard here. It's an example of a platform that would meet the requirements, and chooses to not offer a (compliant) C compiler instead.
- adwn 4y agoI fail to see how their overly clever solution is in any shape or form better than the regular approach shown under "How others declare them" – except for being a "cute" "hack".
- asddubs 4y agowell, obviously the advantage is that you don't have to enumerate all the architectures you support manually. Is it worth it? Ehhh, probably not, but the author isn't exactly advocating for it either
- akling 4y agoI find it neat that the code doesn't have to enumerate all the possible platforms it might be compiled on, and instead just takes something the compiler already provides and tweaks it. But yes, the "cute" "hack" aspect is the primary endearing factor :^)
- deleted 4y ago[deleted]
- self_awareness 4y agoMy thoughts exactly. What if __SIZE_TYPE__ stops being a macro someday? Also double-underscore prefixed symbols are compiler reserved, so any other compiler can use the same symbol for entirely different purpose, so compiler guards are needed when using it. I understand doing hacks when there's no other way around something, or when a hack is much cheaper than the proper solution, but in this case, I think those guys have chosen a more complicated, more expensive, less functional hack than a cheap and more complete proper solution (in "how others are doing it").
- calgarymicro 4y agoWell, he sorta says it at the end: >Other C libraries typically use more careful techniques, such as wrapping the declarations in architecture-specific #ifdefs They don't have to define it multiple times for different architectures. This is theoretically platform agnostic and saves a few lines. Not really that significant, but then again it's not like he's recommending people do it.
- calgarymicro 4y ago> I don’t recommend doing this in your codebase, but it has worked for us so far. :^) The story of my life . . .
- makach 4y agoisn't magical development where you do things just because it "works for me" building technical debt?
- MathMonkeyMan 4y agoThe C standard macro must expand to _something_. My concern would be that it expands to something like __builtin_foo_nugget instead of unsigned long...
- kevin_thibedeau 4y agossize_t isn't in the C standard. The closest substitute is ptrdiff_t.
- MathMonkeyMan 4y agoI was referring to __SIZE_TYPE__, but that isn't in the standard either. Whoops.
- doctor_eval 4y agoIt's definitely not technical debt. The worst that can happen is that you have to go back and do it the "proper" per-arch way at some point. It's just a neat hack.
- rvnx 4y agoTechnical debt is like corporate debt, it's debt that you leverage to get to the market faster and make more money. You may, or may not, have to pay it back. So some technical debt is good in many cases as long as it is properly managed. If you have zero debt it means you are not efficient (the same in finance or if you purchase a home cash while you could get a low and fixed interest rate loan for example).
- garrettgarcia 4y agoI've never thought of it this way before. Great point and analogy!
- denysvitali 4y agoUnrelated to the article per se, but relevant about the author: I really admire Andreas positivity and his ability to spread such joy to the audience. This guy is awesome, and his positivity is outstanding. Just watch a few of his YouTube videos and you'll understand what I mean (:
- kazinator 4y agoI would do it like this: #if SIZEOF_SIZE_T == 8 typedef int64_t ssize_t #elif SIZEOF_SIZE_T == 4 typedef int32_t ssize_t #elif SIZEOF_SIZE_T == 2 // LOL typedef int16_t ssize_t #else #error port me! #endif SIZEOF_SIZE_T can be obtained using a script which compiles a test program without executing it. Over they years I used more than one approach, settling on this one: https://www.kylheku.com/cgit/txr/tree/configure?id=1f902ca63eba9071da5d1da2e861fe49028d32b4#n1487 https://www.kylheku.com/cgit/txr/tree/configure?id=1f902ca63... Here, in the test program, a DEC macro has been defined which given a constant expression, produces two decimal digits as the initializer for a two-character array. For instance: DEC(42) -> { '4', '2' } // not exactly: character constants are not used With this trick we can use DEC(sizeof (size_t)) to get a value like { ' ', '8' } into the portion of some character data, which we can prefix with an identifying string we can look for, like: SIZEOF_SIZE_T= 8 That we can basically grep out. In my configure script, this data is extracted, spaces are removed from it, and it's evaluated directly as shell assignments, so then the values are available in shell variables.
- skissane 4y agoRather than a bunch of #if blocks, one could instead do: #define SIZE_T_BITS 64 #define PASTE3(a,b,c) a##b##c #define TYPEDEF_UINT(bits,name) typedef PASTE3(uint,bits,_t) name #define TYPEDEF_INT(bits,name) typedef PASTE3(int,bits,_t) name TYPEDEF_UINT(SIZE_T_BITS,size_t); TYPEDEF_INT(SIZE_T_BITS,ssize_t); Should work for any value of SIZE_T_BITS, even something weird like 128 or 36 (PDP-10 port?), so long as you already have [u]intN_t defined. It does require SIZE_T_BITS to be in bits rather than bytes, as your SIZEOF_SIZE_T is. But surely if your script can calculate SIZEOF_SIZE_T, it can multiply the answer by 8? (Or by CHAR_BIT, if we want to support weird platforms without 8-bit bytes – Lars Brinkhoff's PDP-10 port of GCC 3.2 has CHAR_BIT==9.)
- kazinator 4y agoThe main thing in my post is how we get that input value, in your case SIZE_T_BITS, at the preprocessing level where we can then use it to select alternative pieces of code. After that it's cosmetic. The simple, dumb #if blocks have the virtue is that they are not hostile to simple tooling, like Exuberant Ctags, Cscope, and whatnot. When we ask the editor to jump to the definition of ssize_t, it knows the three possible places where it is defined and serves them up. This alternative is possible: #define PASTE3(a,b,c) a##b##c #define UINT_TYPE(bits) PASTE3(uint,bits,_t) #define INT_TYPE(bits,name) PASTE3(int,bits,_t) typedef UINT_TYPE(SIZE_T_BITS) usize_t; typedef INT_TYPE(SIZE_T_BITS) ssize_t; I think in this form, there is a good chance the tools will grok the typedefs and index them, because we have not disguised the basic phrase structure.
- nine_k 4y agoThis is a great illustration why C is, on one hand, terrible, and, on the other hand, staunchly practical. Here the trivial, unthinking preprocessor allows to do a pretty crazy (though very understandable and predictable) thing, which allows to acceptably solve a problem which would take years and a ton of effort to be solved "properly" (with standardization and compiler support).
- williamvds 4y agoI agree on the practicality aspect. When programmimg in C, one learns to accept these preprocessor hacks. When programming in C++, where limited compile and type metaprogramming exists, one is constantly hitting the limits and it causes endless frustration. I go through a mini cycle of grief until I give in and use a macro, or an otherwise less elegant implementation. You're right in that it has taken years (decades, even) to standardise some better alternatives to macros. But even now C++ lacks some of their power. I have to wonder how much faster alternatives would have been implemented, if macros weren't "good enough" for so many use cases.
- pjmlp 4y agoMost likely people would be using awk and sed for the same purposes.
- roger10-4 4y agoI wouldn’t call meta- and compile-time programming limited in modern C++ (C++17 and above). With the addition of “constexpr” and “consteval” compile time programming is the same as runtime for many cases. Templates are obtuse for meta programming but usually can get the job done. The need for macros much less common in modern code.
- williamvds 4y agoYou can mostly avoid macros now, thankfully. I'm particularly thinking of things like compile-time reflection, some more complex type introspection. Things that would let you get rid of some code generation like D's mixins perhaps. Existing c++ reflection has mostly been done with macros, which you sacrifice readability for by declaring your class with macros instead, and I believe is often a runtime thing anyway. Complex type metaprogramming is possible, sure, but often so obtuse and illegible I'd dare say the preprocessor is a better alternative if it works.
- fefe23 4y agoHaha funny I came here hoping that someone had found a better solution than me, but you did the same thing. I think __SIZE_TYPE__ is not standard but a gcc extension (that clang also supports). I don't care because my target platform is Linux, but you might. I have doubts about the legality of this solution though. The user might have #defined unsigned and this would break that. So far none of my users was mad enough to do it but I think they would be in their rights if they did. If you only support "standard" platforms you can just typedef signed long ssize_t However some platforms (looking at you here, Windows!) will define long as 32-bit even on 64-bit and for those that will break. Not sure if __SIZE_TYPE__ is intrinsically declared on Windows in the first place. The C standard allows platforms where pointers have more bits than integers, in which case long would not work. Hey, I just had an epiphany. You could use __PTRDIFF_SIZE__!
- Cthulhu_ 4y agoI'm not a C developer at all, but this looks like bragging about a clever trick; when it comes to these things (and most code for that matter), I'd avoid clever tricks like the plague. Just write it out logically and as simple as possible. You're not saving much time / effort by writing out, and clear is better than clever. Code size is never an issue.
- stathibus 4y agoSeriously, in what dimension is this a good choice?
- TheDong 4y agoProgramming is meant to be fun. That sounds like it was fun. For some projects and people, fun is more important than readability, speed, or any other dimension. (edit: not to imply that hack isn't readable or fast or such! I think it's cute and very readable)
- ghosty141 4y agoSerenityOS is not production software or used for anything serious. Its just for fun.
- Y_Y 4y agoIt's fun though. It's nice to have "best practices" etc, but SerenityOS isn't for IBM calculators or NASA life-support systems, it's a project for enjoyment by people who enjoy writing software.
- 0xfedbee 4y agoYou’d understand it if you were a C developer.
- tredre3 4y ago> You’d understand it if you were a C developer. Counterpoint: I'm a C developer and a big fan of Andreas and I prefer mildly clever code over longer explicit code. But even then I was still surprised by his choice to keep this specific hack. It feels very brittle to me.
- strken 4y agoSomewhat off-topic, but what's the :^) supposed to mean? I've always parsed it as "the previous statement was intentionally wrong or irritating", sort of like the /s sarcasm tag except it can also denote trolling those who aren't in on the joke. I'm unsure whether it's used as a normal smiley here or whether there's something I'm missing.
- trashburger 4y agoIn the SerenityOS community, it is intended as a normal smiley face instead of being sarcastic.
- akling 4y agoThat's right! It's a callback to Slackware, which was the first Linux distribution I used as a kid. The release notes for Slackware 4.0 ended with "Have fun! :^)" which I always thought had a great vibe.
- natdempk 4y agoMy understand is that :^) represents a more devious/cheeky smile. I think this is more common than strictly a trolling/sarcasm usage, though you could see how it could fit there as well under my definition.
- layer8 4y agoI don’t know about the OP, but generally speaking it indicates cheekiness.
- CodesInChaos 4y agoRedefining a keyword via macro violates the C++ standard: > Nor shall such a translation unit define macros for names lexically identical to keywords. https://stackoverflow.com/questions/9109377/is-it-legal-to-redefine-a-c-keyword https://stackoverflow.com/questions/9109377/is-it-legal-to-r...
- nickwanninger 4y agoLucky for them, it's a fully custom OS that doesn't follow the standard :)
- Conscat 4y agoThey don't make the compiler. GCC in very many places substitutes standard library names with intrinsics for the purposes of optimization or static analysis. I'm not aware of it doing so for ssize, but in principle this is a bad idea.
- jheriko 4y agothis locks you into the compiler. i used to do "cute" tricks like this. then i learned better. always write the least mysterious code unless you have a good reason...
- Conscat 4y agoActually both GCC and Clang have this feature (so does Circle), and uhh there are plenty of other indispensable features that lock you into those. You need statement expressions and inline assembly to write this kind of software.
- notorandit 4y agoMost Unix-like system inherit that type declaration from the kernel, for obivous reasons. Why not doing the same?
- SAI_Peregrinus 4y agoThey're writing the kernel.
- vrglvrglvrgl 4y ago[dead]