Live data from Hacker News

Tell HN: C Experts Panel – Ask us anything about C

news.ycombinator.com

831–840 of 978 posts

Re: Tell HN: C Experts Panel – Ask us anything about C

#831
post #653

There's a compiler attribute in GCC to promise that a function is pure, i.e. free from side effects and only uses its inputs. This is useful for parallel computations, optimizations and readability, e.g. sum += f(2); sum += f(2); can be optimized to x = f(2); sum += x; sum += x; Would the current motto of the consortium forbid adding a feature such as marking a function as pure, that would not just promise, but also…

No enforcing! This is useful even when it's, strictly speaking, a lie. Suppose I want to add some debug tracing into f(): f.c: 42: f entered f:c: 43: returning 2 that's a side effect, right? But now the pure attribute tells a lie. Never mind though; I don't care that some calls to f are "wrongly" optimized away; I want the tracing for the ones that aren't. In C++ there are similar situations involving temporary objec…

Such attributes would be most useful if the semantics were that any time after a program receives inputs that would cause a "pure" function to be called with certain arguments, a compiler may at its leisure call the function with those arguments as many or as few times as it sees fit.

The notion that "Undefined Behavior" is good for optimization is misguided and dangerous. What is good for optimization is having semantics that are loose enough to give the compiler flexibility in how it processes things, but tight enough to meet application requirements.

Instead of saying that compilers can do anything they want when their assumptions are violated, it would be far more useful to recognize what they are allowed to do on the basis of certain assumptions. For example, given a piece of code:

    long long test1(long long x, int mode)
    {
      while(x)
        x = slow_function_no_side_effects(x);
      return x;
    }

    void long test2(long long x, int mode)
    {
      x = test1(x);
      if (!mode)
        x=0;
      doSomething(x);
    }
It would generally be useful and safe to allow a compiler that determines that no individual action performed by "test1()" could have any side effects may omit the call to "test1()" if its value never ends up being used, without having to prove that the slow function with no side effects will eventually return zero. It is likewise useful and safe to say that if the generated code observes either that the loop exits or that "mode" is zero, it may replace the call "doSomething(x)" with "doSomething(0)". The fact that both optimizations would be safe and useful individually, however, does not imply that it would be safe and useful to allow compilers to change the code for "test2()" so that it calls "doSomething(0)" or otherwise allow code to observe that the value of "x" is zero when mode is non-zero, without regard for whether "test1()" would complete.

Re: Tell HN: C Experts Panel – Ask us anything about C

#832
post #739

C has been making strides towards complete Unicode support. I've been having trouble following along though: Am I correct in assuming that there's no actual multi-byte UTF-8 to UTF-32 Rune function and the best approximation depends on whatever wchar_t is? How would I best handle pure Unicode input and output scenarios on a "hostile" OS whose native character encoding is some EBCDIC abomination or a Windows codepage?

Are you looking for mbstowcs() or mbtowc() ?

wchar_t can be (a) not Unicode in any way, or (b) 16-bit, insufficient to represent a rune.

Re: Tell HN: C Experts Panel – Ask us anything about C

#833
post #829

Earlier quoted context omitted.

Many programs are subject to two constraints: 1. Behave usefully when practical, if given valid data. 2. Do not behave intolerably, even when given maliciously crafted data. For a program to be considered usable, point #1 may be sometimes be negotiable (e.g. when given an input file which, while valid, is too big for the available memory). Point #2, however, should be considered non-negotiable. If integer calculation…

Intolerable is too situation specific. Integer overflows that yield "weird values" in one place can easily lead to disasterous bugs in another place. So the safest thing in general would be to abort on integer overflow. But I'm sure there are applications where that, too, is intolerable. Kinda hard to have constraint 2 then.

Having a program behave in unreliably uselessly unpredictable fashion can only be tolerable in cases where nothing the program would be capable of doing would be intolerable. Such situations exist, but they are rare.

Otherwise, the question of what behaviors would be tolerable or intolerable is something programmers should know, but implementations cannot. If implementations offer loose behavioral guarantees, programmers can determine if they meet requirements. If an implementation offers no guarantees whatsoever, however, that is not possible.

If the only thing about overflow is that temporary values may hold weird results, and if certain operations upon a "weird" result (e.g. assignment to anything other than an automatic object whose address is never taken) will coerce it into a possibly-partially-unspecified number within type's range, then a program may ensure that behavior will be acceptable regardless of what weird values result from computation.

According to the published Rationale, the authors of C89 would have expected that something like:

    unsigned mul(unsigned short x, unsigned short y)
    { return (x*y); }
would on most implementations yield an arithmetically-correct result even for values of (x*y) between INT_MAX+1U and UINT_MAX. Indeed, I rather doubt they could imagine any compiler for a modern system would do anything other than yield an arithmetically-correct result or--maybe--raise a signal or terminate the program. In some cases, however, that exact function will disrupt the behavior of its caller in nonsensical fashion. Do you think such behavior is consistent with the C89 Committee's intention as expressed in the Rationale?

Re: Tell HN: C Experts Panel – Ask us anything about C

#834
post #409
post #151

Earlier quoted context omitted.

> no such feature has emerged in practice Arrays with length constantly emerge among C users and libraries. They are just all incompatible because without standardization there is no convergence.

typedef struct {uint8_t *data; size_t len;} ByteBuf; is the first line of code I write in a C project.

Could you add some extra information why this is so helpful or handy to have? Think it will benefit readers that are starting out with C etc.

Re: Tell HN: C Experts Panel – Ask us anything about C

#835
post #336

Earlier quoted context omitted.

No, it's exactly the opposite. Without UB the compiler must assume that the corner case may arise at any time. Knowing it is UB we can assert `n+1 > n`, which without UB would be true for all `n` except INT_MAX. Standardising wrap-on-overflow would mean you can now handle that corner case safely, at the cost of missed optimisations on everything else.

Have you considered adding intrinsic functions for arithmetic operations that _do_ have defined behavior on overflow. Such as the overflowing_* functions in rust?

The semantics most programs need for overflow are to ensure that (1) overflow does not have intolerable side effects beyond yielding a likely-meaningless value, and (2) some programs may need to know whether an overflow might have produced an observably-arithmetically-incorrect result. A smart compiler for a well-designed language should in many cases be able to meet these requirements much more efficiently than it could rigidly process the aforementioned intrinsics.

A couple of easy optimizations, for example, that would be available to a smart compiler processing straightforwardly-written code to use automatic overflow checking, but not to one fed code that uses intrinsics:

1. If code computes x=yz, but then never uses the value of x, a compiler that notices that x is unused could infer that the computation could never be observed to produce an arithmetically-incorrect result, and thus there would be no need to check for overflow.

2. If code computes xy/z, and a compiler knows that y=z*2, the compiler could simplify the calculation to x+x, and would thus merely have to check for overflow in that addition. If code used intrinsics, the compiler would have to overflow check the multiplication, which on most platforms would be more expensive. If an implementation uses wrapping semantics, the cost would be even worse, since an implementation would have to perform an actual division to ensure "correct" behavior in the overflow case.

Having a language offer options for the aforementioned style of loose overflow checking would open up many avenues of optimization which would be unavailable in language that only over precise overflow checking or no overflow checking whatsoever.

Re: Tell HN: C Experts Panel – Ask us anything about C

#836

Earlier quoted context omitted.

Eh? I thought that would only be "legal" if it was specified to be implementation-defined behavior. Which would, frankly, be perfectly good. But since it is specified as undefined behavior, programmers are forbidden to use it, and compilers assume it doesn't happen/doesn't exist. The entire notion that "since this is undefined behavior it does not exist" is the biggest fallacy in modern compilers.

The rule is: If you want your program to conform to the C Standard, then (among other things) your program must not cause any case of undefined behavior. Thus, if you can arrange so that instances of UB will not occur, it doesn't matter that identical code under different circumstances could fail to conform. The safest thing is to make sure that UB cannot be triggered under any circumstances ; that is, defensive prog…

Where does that myth come from!? According to the authors of C89 and C99, Undefined Behavior was intended to, among other things, "identify areas of conforming language extension" [their words]. Code which relies upon UB may be non-portable, but the authors of the Standard expressly did not wish to demean such code; that is why they separated out the terms "conforming" and "strictly conforming".

Re: Tell HN: C Experts Panel – Ask us anything about C

#837

Any plans to add semantics for exceptional situations such as divide by zero and dereferencing a null pointer? https://blog.regehr.org/archives/232 Or incorporating features from this 14 item list? https://blog.regehr.org/archives/1180 As it appears these have failed: https://blog.regehr.org/archives/1287

Consider the following function:

    int test(int a, int b)
    {
      int c = a/b;
      if (f1())
        f2(a,b,c);
    }
Should a compiler be required to compute c before calling f1, and thus have to store the value of c across the function call?

Better would be to define a set of semantics for loosely-sequenced traps, along with "causality barriers" to ensure that they only occur at tolerable times.

Re: Tell HN: C Experts Panel – Ask us anything about C

#838

Do you think that static analysis is a valuable tool for security research? Do you recommend static analysis software to a single developer with a limited budget or an amateur?

would love to see a couple of detailed comments on this directly as well, I know that one of yall is a maintainer of an analyzer, maybe just some general discussion on beginning to learn C while at the same time incorporating a static analyzer and what that would look like.

Re: Tell HN: C Experts Panel – Ask us anything about C

#839

How do you join three float values into a comma separated string, and then split it again?

Not sure what you mean but would s8 buf[enoughspace]; snprintf(buf, sizeof(buf), "%f,%f,%f", your, three, values); sscanf(buf, "%f,%f,%f", &your, &three, &values); Do the job?

I think that the GP was making a commentary on the sorry state of locale handling in C.

You need to first store the current locale, change the locale to one that doesn't use a comma as the decimal point, perform the above, and set the locale back. Plus, there's no threadsafe way to do this, since the locale is process-wide.

Re: Tell HN: C Experts Panel – Ask us anything about C

#840
post #829

Earlier quoted context omitted.

Intolerable is too situation specific. Integer overflows that yield "weird values" in one place can easily lead to disasterous bugs in another place. So the safest thing in general would be to abort on integer overflow. But I'm sure there are applications where that, too, is intolerable. Kinda hard to have constraint 2 then.

Having a program behave in unreliably uselessly unpredictable fashion can only be tolerable in cases where nothing the program would be capable of doing would be intolerable. Such situations exist, but they are rare. Otherwise, the question of what behaviors would be tolerable or intolerable is something programmers should know, but implementations cannot. If implementations offer loose behavioral guarantees, program…

> Do you think such behavior is consistent with the C89 Committee's intention as expressed in the Rationale?

No, but in general I'm ok with integer overflows causing disruptions (and I'm happy that compilers provide an alternative, in the form of fwrapv, for those who don't care).

I do think that the integer promotions are a mistake. I would also welcome a standard, concise, built-in way to perform saturating or overflow-checked arithmetic that both detects overflows as well as allows you to ignore them and assume an implementation-defined result.

As it is, preventing overflows the correct way is needlessly verbose and annoying, and leads to duplication of apis (like reallocarray).

Post reply on HN