Live data from Hacker News

Essential C (2003) [pdf]

cslibrary.stanford.edu

41–50 of 84 posts

Re: Essential C (2003) [pdf]

#41
post #24

Earlier quoted context omitted.

> Indices are difference types Umm, no? Indices are ordinals[0], forming the canonical/nominal well-ordering of a collection such as a array. > an ordinal number, or ordinal, is one generalization of the concept of a natural number that is used to describe a way to arrange a (possibly infinite) collection of objects in order, one after another. [...] Ordinal numbers are thus the "labels" needed to arrange collections…

In C an index is a difference that you add to a pointer to get a pointer. `a[i]` is `*(a + i)`. Given two indices `i` and `j`, you want `i - j` to be such that `a[j + (i - j)]` is `a[i]`, and it then makes sense to me that `i - j` is signed. The expression works out whether they are signed or unsigned, but just in terms of their interpretation on the part of a user (eg. "oh this is 2 elements before bc. it says -2")…

ptrdiff_t exists for subtraction between pointers that produce negative values. But how many times have you ever needed to subtract p and q where p represents an array element at a higher index than q? For that matter, how many times have you ever needed to add a negative integer to a pointer?

In C an object can be larger than PTRDIFF_MAX, a real possibility in modern 32-bit environments. (Some libc's have been modified to fail malloc invocations that large, but mmap can suffice.) Because pointer subtraction is represented as ptrdiff_t, the expression &a[n] - a could produce undefined behavior where n is > PTRDIFF_MAX. But a + n is well defined behavior for all positive n (signed or unsigned) as long as the size of a is >= n.

There's an asymmetry between pointer-pointer arithmetic and pointer-integer arithmetic; they behave differently and have different semantics. Pointers are a powerful concept, but like most powerful concepts the abstraction can leak and produce aberrations. I realize opinions vary on whether to prefer signed vs unsigned indices and object sizes (IME, the camps tend to split into C vs C++ programers), but the choice shouldn't be predicated on the semantics of C pointers because those semantics alone don't favor one over the other.

Re: Essential C (2003) [pdf]

#42

I don't want to learn C but certain types of applications there's really no practical alternative.

I typically see Rust marketed as a better replacement for C. In what cases isn't it a practical replacement?

Its a replacement for c++ not c

Re: Essential C (2003) [pdf]

#43
post #33

Earlier quoted context omitted.

Yeah, so then you write for (size_t i = n-1; i It works fine (unsigned overflow is well defined) but it's even less clear.

size_t is unsigned? Since when?

It always has been. C89, 4.1.5[1]:

> The type are [...] size_t which is the unsigned integral type of the result of the sizeof operator

(Emphasis mine.)

1. https://port70.net/~nsz/c/c89/c89-draft.html#4.1.5

Re: Essential C (2003) [pdf]

#44

> Relying on the difference between the pre and post variations of these operators is a classic area of C programmer ego showmanship. Ugh. He's talking about inline use of post-increment and pre-increment (i.e. x++ and ++x) here. This is perfectly readable to a C programmer, and sidestepping them actually makes the code harder to understand.

Can you give an example where not using them 'inline' makes code harder to understand?

Imagine operating on something like a stack.

  x = *stack--; // pop 'x' off of the stack
  *++stack = y; // push 'y' onto the stack
This way is simple, direct, and it avoids inconsistent state.

Re: Essential C (2003) [pdf]

#45

This guide is short (which is always nice) but not has a couple of flaws in places in the brief skim I gave it. For example: > In particular, if you are designing a function that will be implemented on several different machines, it is a good idea to use typedefs to set up types like Int32 for 32 bit int and Int16 for 16 bit int. Use please > The char constant 'A' is really just a synonym for the ordinary integer val…

> Under the assumption that there are no boolean types from earlier, this is not true Can you elaborate on this one? I thought && and || expressions always evaluated to 0 or 1.

C99 added type _Bool (also called bool if you #include ) -- but it doesn't actually use it much. Operators that yield logically "boolean" values (, >=, ==, !=, &&, ||, !) yield values of type int, not _Bool. The value is always 0 or 1 -- in contrast with isdigit(), for example, which is specified to return 0 for false and some unspecified non-zero value for true.

Converting any scalar value (that includes pointers) to _Bool yields 0 or 1 (false or true).

Re: Essential C (2003) [pdf]

#47

Earlier quoted context omitted.

Can you give an example where not using them 'inline' makes code harder to understand?

Imagine operating on something like a stack. x = *stack--; // pop 'x' off of the stack *++stack = y; // push 'y' onto the stack This way is simple, direct, and it avoids inconsistent state.

    int pop_int () 
    {
      int x = *stack; 
      --stack;
      return x;
    }

    void push_int(int x)
    {
      ++stack;
      *stack = x;
    }

Genunine questions:

- Is this worse? - How does the state get inconsistent?

Re: Essential C (2003) [pdf]

#48

Earlier quoted context omitted.

Can you give an example where not using them 'inline' makes code harder to understand?

Imagine operating on something like a stack. x = *stack--; // pop 'x' off of the stack *++stack = y; // push 'y' onto the stack This way is simple, direct, and it avoids inconsistent state.

I don't see how this proves the point.

For someone who doesn't have the operator precedence rules memorized, it isn't clear whether the above code means this:

    x = *stack;
    stack--;
or this:

    stack--;
    x = *stack;
Combining those two operations into one line is a trade-off I will never agree with. And I'm a fan of C myself: https://gist.github.com/cellularmitosis/3327379b151445c602ad... https://gist.github.com/cellularmitosis/d8d4034c82b0ef817913...

The two-liner is actually the one which is simpler and more direct, as it requires less knowledge of operator precedence rules. The one-liner and two-liner compile to the same number of instructions, so I don't see how either "avoids inconsistent state".

Many expert-level C programmers tend towards one-liners. Here's an example from the original "Red book":

    c = ((((i&0x8)==0)^((j&0x8))==))*255;
nooooo don't do it sadpanda.jpg

Re: Essential C (2003) [pdf]

#49

I don't want to learn C but certain types of applications there's really no practical alternative.

I typically see Rust marketed as a better replacement for C. In what cases isn't it a practical replacement?

When you’re targeting platforms that Rust doesn’t, have a lot of legacy code that can’t do for a new toolchain, or cannot handle dependencies.

Re: Essential C (2003) [pdf]

#50
post #47

Earlier quoted context omitted.

Imagine operating on something like a stack. x = *stack--; // pop 'x' off of the stack *++stack = y; // push 'y' onto the stack This way is simple, direct, and it avoids inconsistent state.

int pop_int () { int x = *stack; --stack; return x; } void push_int(int x) { ++stack; *stack = x; } Genunine questions: - Is this worse? - How does the state get inconsistent?

For one it’s three and two lines for what is two logical operations. I assume the “inconsistent state” is the time between the lines where the stack is not truly in the right state-many people prefer to preserve their invariants as much as possible.
Post reply on HN