13 ms·
What is C in practice?
- ydcvjk 11y agoA lot of C programmers make valid assumptions based on their system architecture and compiler. Is there any point trying to unify their obviously different practices, while at the same time ignore the Standard? No.
- MichaelCrawford 11y agoTo me, the whole point of C is to enable one to take advantage of one's system architecture. Thats not really possible in higher level languages.
- pcwalton 11y agoWell, one of the interesting takeaways from the article is that C as specified often does not let you take advantage of your system architecture. The specification has things like GPUs and segmented memory architectures in mind when it forbids you from doing seemingly reasonable things like taking the difference between the addresses of two separately-allocated objects, even though chances are very good what you're trying to do works just fine on all architectures you care about.
- AnimalMuppet 11y agoYes and no. The standard specification defines a C that is very portable. It is quite reasonable that you cannot portably take advantage of your system architecture, which means you cannot do it using C as defined by the standard. But you still can write stuff that looks just like C, that a C compiler will accept, and that lets you take advantage of your system architecture. You just need to understand that a new version of your compiler can break all of your nifty tricks.
- ydcvjk 11y agoFinally someone that understands.
- Gibbon1 11y agoNot really because there are two kinds of c programer out there. 1. Those that communicate with the outside world exclusively via library calls to an operating system. 2. Those that do not. People that live in world #1, usually don't get people living in world #2. Example. // wait for write ready while((spi.S & SPI_S_SPTEF_MASK) == 0) ; spi.D; spi.D = data; Guess what, No spi.D; is actually important. No just because the program never writes to spi.S does not mean you can delete the while loop. No you cannot reorder any of this and have it work.
- spdionis 11y agoWhy? That's the issue that people should solve. Disclaimer: I have no C experience.
- deleted 11y ago[deleted]
- deleted 11y ago[deleted]
- _dps 11y agoAre you saying something other than "you, the programmer, must declare spi to be volatile, or else the compiler might lay a bear trap for you?". Based on your comment it seems clear that you understand that... but given that you understand that I don't get the point you're trying to make (except perhaps that writing low-level hardware code in C often means actively stopping the compiler from screwing you over). (for non-C programmers: "volatile" can be approximated by "hey compiler, this variable can change unexpectedly even if you didn't do anything to change it, so don't use any optimizations that assume you're the only one changing it).
- ydcvjk 11y ago
- MichaelCrawford 11y agoIve worked on embedded systems where stuff like that would be a problem, howver I also knew the memory map for my target. I wanted to make some crypto run faster so i used memcpy on a pointer to a function to copy executable code from slow flash to fast ram.
- noselasd 11y agoI suspect the parent is more referring to people writing code that does e.g. foo(i++, i++); or e.g. relying on signed integer overflow behvior based on observing a toy program on their own machine - assuming the code will behave the same in all contexts, optimization levels or minor versions of the compiler
- jerf 11y ago"take advantage of one's system architecture." Yes, if your system is essentially a PDP-11. I don't mean that sarcastically; pcwalton's sibling message only begins to mention the ways in which C is not a match to modern systems. Vector processing, NUMA, umpteen caching layers, CPU features galore... it's not really a match to the "architecture" any more. (To the extent that you may think C supports those things, I don't really think "lets you drop arbitrary assembler in the middle of a function" constitutes "support". YMMV. To be fair to C there's a lot of features that seem to be unsupported by any high-level language today. Hardware moves way faster than programming languages. If you want to figure out what's coming after the current generation of languages, "a language that actually lets you use all the capabilities of modern hardware without dropping to assembler and giving up all the safety of the higher-level language" is at least one possible Next Big Thing.)
- arielby 11y agoC matches NUMA and caching just as well as assembly does. Basically the only thing C doesn't really expose is SIMD, but compiling "high-level" (i.e. C-level or above) code into SIMD is an open research problem (AFAIK) - I mean, you can easily compile array programs into SIMD, but the more general case is still open.
- pjmlp 11y agoReally?! How do you specify alignment and packing in ANSI C?
- stephencanon 11y ago_Alignof( ) and _Alignas( )?
- pjmlp 11y agoYeah, since C11 only. However very few compilers, specially in the embedded and real time OS space do offer C11 compliance. Gcc and clang aren't the only game in town. If one aims to write compliant C code much more compilers come into the picture.
- pjmlp 11y agoIt is also not possible in C. People just think C is any better. The standard doesn't say anything about SIMD, GPU, instruction reording, IO registers, interrupts... All of that are language extensions or library functions written in Assembly. Any programming language can offer similar extensions.
- Retra 11y agoNo, the whole point of C is to abstract away one's system architecture. C is a higher level language in this sense.
- Animats 11y agoMany C programmers assume a single flat memory space. Most machines today have that. There's a long history of machines that didn't: Intel 286 in segmented mode, some Pentium variants in segmented mode beyond 4MB (Linux supports this in the kernel), IBM AS/400, Unisys Series B 36-bit word machines, Burroughs 5500/6700/Unisys Series A segmented machines where an address is a file-like path, Motorola CPUs where I/O space and memory space are distinct, and old DEC PDP-11 machines where code and data address spaces are separate. More recently, there are non shared memory multiprocessors where addresses are duplicated across processors - the PS3's Cell and many GPUs, for example. Most programmers today will never encounter any of those.
- kevin_thibedeau 11y agoThere's also the assumption that null is address 0. Notably breaking the purity of C++s type system with magic 0s for 20 odd years before nullptr came along.
- michaeljsmith 11y agoActually the C++ spec says that 0 as a pointer doesn't actually have to have the value 0 - it just refers to a null value the same way as nullptr.
- kevin_thibedeau 11y agoThat is the point. They went through the process of tightening down the type system by invalidating automatic casts and making void* less promiscuous and then they break that whole philosophy with a magic literal "0" that can work as a normal integer or as an address placeholder for any pointer even though it won't necessarily evaluate to address zero. At that point why can't I assign "0" to a float too and enjoy some more magic there?
- Kenji 11y agoThis is why we need to implement everything in JavaScript hides
- krylon 11y agoApparently, to some people, irony is an undefined behavior... (Just to be clear, I am referring to the people that downvoted you.)
- Kenji 11y agoGood one. Too bad humour is not tolerated here :)
- gjm11 11y agoAs I've said before: the thing is, the HN crowd is a really tough audience -- they will only be amused by jokes if they're actually funny. If your idea of humour is "make some pop-culture reference and it will automatically be funny" or "be generically cynical about everything and it will automatically be funny" then, yeah, you're going to have a difficult time on HN. Something like 1% or so of my HN comments are jokes. They usually do pretty well for karma. (Slightly to my surprise, the most recent one to flop completely was one that was also making a slightly serious point, and one that I don't think many HN readers would disagree with. Ah well, can't win 'em all.)
- timtadh 11y agoQuestion 2 is : Is reading an uninitialised variable or struct member (with a current mainstream compiler): (This might either be due to a bug or be intentional, e.g. when copying a partially initialised struct, or to output, hash, or set some bits of a value that may have been partially initialised.) a) undefined behaviour (meaning that the compiler is free to arbitrarily miscompile the program, with or without a warning) : 128 (43%) b) ( * ) going to make the result of any expression involving that value unpredictable : 41 (13%) c) ( * ) going to give an arbitrary and unstable value (maybe with a different value if you read again) : 20 ( 6%) d) ( * ) going to give an arbitrary but stable value (with the same value if you read again) : 102 (34%) e) don't know : 3 ( 1%) f) I don't know what the question is asking : 2 ( 0%) -------------------- I know of one datastructure (a sparse set of integers from 1-n) which relies on this behavior: http://research.swtch.com/sparse http://research.swtch.com/sparse . I always thought it was a neat trick. However, from the article is seems that may NOT give stable values to uninitialized members. Which may make that data structure behave strangly or cause the program to miscompile.
- JoshTriplett 11y agoThat's a question for which multiple answers are correct: a, b, and c.
- ustolemyname 11y agoI disagree regarding b. I think you'll find the outcome of this expression quite predictable: int b; int c = b * 0;
- JoshTriplett 11y agoSure, if the expression doesn't depend on the value at all, it probably won't have unpredictable results. (Though as with any kind of undefined behavior, don't count on it, as compilers can be "clever" sometimes.)
- DSMan195276 11y agoGenerally speaking, it depends on where the uninitialized value is located (Though, AFAIK, by the standard they are all to be considered unstable for the most part). The big catch is stack-allocated uninitialized variables. What gcc may do (and I presume clang/LLVM too) is assign an uninitialized variable to a register, but then never give it any stack-space. So, what happens is that the code my always treat register 'a' as though it contains variable 'i', but since variable 'i' is uninitialized, it never allocates or reads anything from the stack, so the value of 'i' just becomes whatever happens to be inside of register 'a' before you attempted to use it. This would cause the variable to appear to randomly change from one value to another, even during single statements, depending on what register 'a' get's used for. For the sparse set of integers, generally speaking that's going to be allocated somewhere else all at once, so there is not much the compiler can do to 'realize' it's uninitialized and decide to just ignore the read from memory completely. A fancy compiler could presumably flag every uninitialized location, then do checks and use some random value every-time you attempt to use one, but practically speaking no compiler is going to do that, so this data-structure isn't technically standards compliant, but it should probably still work anyway.
- nkurz 11y agoIt remains unclear what behaviour compilers currently provide (or should provide) for this. It might be nice if future surveys explicitly asked a followup question "Regardless of the standard or behavior of existing compilers, is there one of these answers that is the 'obviously correct' manner in which compilers should behave? Which one?" If practically all users believe that the same answer is 'obviously correct', compiler writes might want to take this into account when deciding which behavior to implement. For MSVC, one respondent said: "I am aware of a significant divergence between the LLVM community and MSVC here; in general LLVM uses "undefined behaviour" to mean "we can miscompile the program and get better benchmarks", whereas MSVC regards "undefined behaviour" as "we might have a security vulnerability so this is a compile error / build break". First, there is reading an uninitialized variable (i.e. something which does not necessarily have a memory location); that should always be a compile error. Period. Second, there is reading a partially initialised struct (i.e. reading some memory whose contents are only partly defined). That should give a compile error/warning or static analysis warning if detectable. If not detectable it should give the actual contents of the memory (be stable). I am strongly with the MSVC folks on this one - if the compiler can tell at compile time that anything is undefined then it should error out. Security problems are a real problem for the whole industry and should not be included deliberately by compilers." I'm much less familiar with MSVC than the alternatives, but this is a refreshing approach. Yes, give me a mode that refuses to silently rewrite undefined behavior. Is MSVC possibly able to take this approach because it isn't trying to be compliant to modern C standards? Does it actually reduce the ability to apply useful optimizations? Or just a difference in philosophy?
- dllthomas 11y agoWhile I am quite sympathetic to the "break the build on undefined behavior" position, I have a nit to pick in the quoted passage. Initialization of a variable has no relation to whether it has a memory location. You can legitimately take the address of an uninitialized variable (that is often one step in initializing them, such as when you pass the address to memset), and even an initialized variable may not have a memory location (if it lives only in register, or is compiled away entirely).
- userbinator 11y agoThere is a relevant article http://blog.regehr.org/archives/1180 http://blog.regehr.org/archives/1180 and discussion https://news.ycombinator.com/item?id=8233484 https://news.ycombinator.com/item?id=8233484 about what programmers really think C should be like (i.e. the "portable assembler" it was designed to be originally); it incidentally also shows what they think a sane machine architecture should be like.
- marcosdumay 11y agoIt's scaring to notice that all this is undefined behavior. Hell, glibc network functions rely on undefined behavior!
- deleted 11y ago[deleted]
- programmernews3 11y agoAs far as it's possible compilers should check as much as possible when false assumptions are made. Is it possible to build a language, which would reduce the number of false assumptions?
- lsiebert 11y agoInterestingly git used bit fields in it's code.
- aidenn0 11y agoNote that the C99 (I'm not up on C11 yet) standard specifically allows type-punning through the use of unions (and disallows it in essentially all other cases except when one type is a character type). Also, there seems to be some confusion about storing and loading pointers, when the standard speaks to this as well; roughly: a pointer which is converted to a "large enough" integer type will point to the same object when converted back. It is permissible for an implementation to not provide a large enough integer type, but excepting that, the behavior is well defined.
- bshanks 11y agoAs noted in the article, a summary of this document (about one-third the length) from the same authors is available at http://www.cl.cam.ac.uk/~pes20/cerberus/notes51-2015-06-21-survey-short.html http://www.cl.cam.ac.uk/~pes20/cerberus/notes51-2015-06-21-s...