1. 8

  2. 11

    While the examples convey the idea, there are a number of issues with using them for teaching C. For instance:

    • The macros are missing parenthesis around the macro parameters. SetBit(A, i + 1) will not expand to what you want
    • There is no range checking
    • It assumes that int is 32-bit. The standard only specifies a minimum range for int which is 16 bits. You likely want to use one of the fixed-width integer types from C99 like uint32_t.
    • Signed integers do not necessarily allow you to use all possible bit patterns. Signed int might not be two’s complement, and there could be values that are trap representations (I know this is entering the realm of the unlikely, but if you want portable C you have consider this).
    1. 7

      You could also use CHAR_BIT * sizeof(unsigned int) to get the number of bits in an unsigned integer (which is better suited for this type of job). Both are constants, so there should be no runtime overhead for calculating that result. I would also be inclined to make the functions static inline (a C99 feature) and define them in a header. That avoids any issues with macros while still producing decent code. Something like:

      #include <limits.h>
      #include <stdlib.h>
      static inline void setbit(unsigned int *a,size_t size,size_t idx)
        size_t       off = idx / (CHAR_BIT * sizeof(unsigned int));
        unsigned int bit = idx % (CHAR_BIT * sizeof(unsigned int));
        unsigned int val = 1u << bit;
        if (off >= size) abort();
        a[off] |= val;
      1. 3

        Out of a really morbid curiosity, are there any modern common platforms where CHAR_BIT isn’t just 8?

        1. 5

          It’s very hard to say, as I’ve found very scant information about non-byte-oriented, non-2’s-complement C compilers. The Unisys 1100/2200 use 9 bit characters and they are still in use (for various values of “use”). I’ve also heard that C compilers for DSPs tend to have to define char as larger than 8-bits, but I’ve never worked with a DSP so I can’t say for sure.

          1. 2

            Any modern platform that is, is not POSIX-compliant.

            1. 2

              The standard (indirectly) requires CHAR_BIT to be at least 8. I’ve never worked on a platform where it wasn’t 8, but have heard about embedded devices where it was 16 or 32 – a quick web search found mention of such as well [1].

              I think one important thing the UB debacles over the recent years has shown is that if you rely on behaviour not guaranteed by the standard, your code may work right now with the current compiler on the current hardware, but may break randomly in the future.

              [1] https://stackoverflow.com/questions/32091992/is-char-bit-ever-8/38360262

          2. 1

            Signed int might not be two’s complement,

            Are there modern common architectures where this is not the case?

            1. 3

              I’m not aware of any non-2’s-complement machines made since the 70s, but as I’ve learned over the years, there are always exceptions. So one may be lurking out there [1]. I’m also not aware of any 2’s complement machines that have a trap representation, but I do know of several 2’s complement byte-addressable machines that can trap on overflow—the VAX [2] and MIPS [3].

              [1] IEEE-754 floating point math is sign-magnitude, and that’s in wide use these days.

              [2] There’s a flag in the condition codes register that enables/disables such trapping.

              [3] The ADD instruction will trap on overflow, but all compilers I’ve used on MIPS default to ADDU, which does not trap.

          3. 6

            I think we have a variation on this theme in illumos: https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/os/bitset.c

            1. 5

              it might be better to gain a firmer understanding of the language before posting tutorials. It’s good to help people but the previous code was tested without even “-Wall”


              1. 2

                While probably from system’s perspective rather simple, I thought this was clever! Goes to show how bit operations and macros can really clean up an efficient idea.

                1. 2

                  As is usual for C, this introduction relies on unportable behaviour, the macros aren’t safe in even trivial ways, and the implementation is marked by other issues.

                  In Common Lisp and Ada, I can very easily declare such arrays and get the full breadth of bounds-checking and whatnot, without the need to wastefully do this myself, as the article states:

                  The C programming language provides all the necessary operations to allow the programmer (= you) to implement an “array of bits” data structure.

                  That’s really something that should be done for me, but C is at the same time high level and inefficient, while also being unsuited to true low-level programming in any decent way.

                  Moving on, Common Lisp permits bit arrays, boolean arrays, and treating integer types as their twos’ complement representation of bits. I’m not guaranteed to get the most efficient implementation, but that’s because Common Lisp is a high-level language. Furthermore, since Common Lisp supports operations such as LDB, DPB, MASK-FIELD, and DEPOSIT-FIELD for doing these manipulations and more, an implementation that does support the efficient representation can also very easily use whatever instructions the machine provides for doing this, whereas the C lacks this and relies on either nonstandard compiler-provided functions being used or extremely-specific patterns being detected by the compiler and transformed into such instructions, which is a big reason a language such as Common Lisp can very easily have a smaller and less complicated compiler than a ’‘simple’’ language such as C.

                  With Ada, it’s simple to see how such arrays are supported and Ada also provides features for manipulating some number types in similar ways. Ada has a very high-level notion of an array that permits a compiler to choose an optimal representation and, unlike Common Lisp, it’s perhaps more likely a compiler will in every case, such as with an array of booleans.

                  I did like this page’s CSS.

                  1. 8

                    Your comment would be a lot more actionable I think if you gave examples (actual code!) of the things you talk about, both in C and other languages.

                    I think a lot of people are so numb to others saying “b-b-b-b-b-but undefined behavior, use $PET_LANGUAGE” that we don’t really care unless given both the precise example of UB and also sample code in the other language for how it’s handled.

                    tl,dr; code snippets over flamebait.

                    1. [Comment removed by author]

                      1. 4

                        I find both of them to be normal comments, but if we had to pick the flamebait, I would pick the one that starts with "As is usual for C, this introduction relies on unportable behaviour, the macros aren’t safe in even trivial ways, and the implementation is marked by other issues." rather than the one that focuses on actionability.

                        Hell, just look at the difference in their posting history, u/friendlysock’s doesn’t just seem much less like “I want to provoke a debate on my terms” as compared with u/verisimilitude’s, but is actually more flame-free than the latter’s as well.