Hi Branden,

On 2026-02-22T13:04:15-0600, G. Branden Robinson wrote:
> Hi Alex,
> 
> At 2026-02-22T12:47:58+0100, Alejandro Colomar wrote:
> > On 2026-02-21T21:01:36-0600, G. Branden Robinson wrote:
> > > At 2026-02-21T23:52:08+0100, Alejandro Colomar wrote:
> > > > I'd like to know why you did that.
> > > > 
> > > >         commit 4b7a0fe5ab5d5155bd499cf9506a91a1f4bc0125
> > > >         Author: G. Branden Robinson <[email protected]>
> > > >         Date:   2025-12-06 18:26:02 -0600
> > > > 
> > > >             src/utils/xtotroff/xtotroff.c: Fix code style nit.
> > > >             
> > > >             * src/utils/xtotroff/xtotroff.c:
> > > >               (CanonicalizeFontName, FontNamesAmbiguous, MapFont, main):
> > > >               Explicitly cast unused return values of printf(3)-family
> > > >               functions to `void`.
> > > > 
> > > > I've been updating code to remove those casts, because they don't
> > > > do much good.  It's essentially just noise.
> > > 
> > > I disagree.  I feel that one should never call a function with no
> > > evident awareness of its return type.  (If its return type is
> > > "void", then no cast should be made!)  I acknowledge that
> > > type-slovenliness is a proud tradition in C, but I reject it.  It
> > > only makes life harder for medium- to large-scale projects.
> > 
> > Isn't "I don't read the return value" an proof of evident awareness
> > that either a) a function has no return value or b) it has a value we
> > don't care about?
> 
> I'm not convinced of that.

Please explain why not.  If you do this, everything can be disambiguated
by checking the documentation.  If you don't, then it's not possible,
and the reader of the code has to choose between trusting you or not
trusting you.  And whenever I read such code, I tend to think "the
programmer seems to be one of those that wants others to assume it knew
what it was doing, but that proves it was rather dumb, so I'm going to
assume everything that programmer did can be deleted".  I'd consider
a cast is a sign of what you and I critisize.

> > Why would it be important to differentiate those two?  And even more
> > importantly, when a mistake is made, how do we differentiate a) the
> > programmer know more than us and knew it could discard the value vs b)
> > the programmer was clueless and dumbly dumped the value?
> 
> As a general rule these two cases are hard to distinguish, especially in
> "terse" languages like C that historically were designed to meet the
> needs of a small pool of closely collaborating practitioners of high
> average competence who were often also experts in production of assembly
> language programs for the only machine that the language then targeted,
> the DEC PDP-11.
> 
> Much about Pascal has been derided by C partisans because Wirth
> explicitly designed Pascal as a teaching language.  In my opinion (and
> others', famously Kernighan's), he punted too many language features,[1]
> but his attention to the needs of language learners was, I maintain, not
> ill-conceived in the least.
> 
> C advocates trumpeted loudly that theirs was a language for people who
> know what they were doing; many of these same advocates went on to prove
> that they themselves didn't.  The ill-kept secret is that _everybody_
> has moments where they don't know what they're doing.  Much more
> mischief arises from attempts to conceal this fact than from designing
> systems around the expectation of it.  This is why lint(1) got written
> even before C became portable.  Or around the same time, anyway.[3]

But casts are precisely _the_ tool to tell lint "I know better, don't
mess with me".  Casts turn off lint(1) and any compiler diagnostics.
That's precisely why I think it's ironic that you're using casts,
because that's precisely what you're criticizing.

> > > Languages like Pascal and Ada distinguish "procedures" (which affect
> > > the state of the system only via "side effects") from "functions"
> > > that might be "pure", but even if not, generally communicate
> > > information back to their callers via return values.
> > 
> > We have something close in C: __attribute__((__unsequenced__)).
> 
> Not looking it up...but reasoning from my crude understanding of C's
> definition of a "sequence point", which involves the semantics of memory
> ordering, in which there are (only?) two major approaches, total-store
> ordering (TSO) and acquire-release semantics, I think I can see how one
> can apply "unsequencedness" to a "pure function".

You could think of unsequenced functions as a set of constraints (which
BTW I want to propose to the committee, to make it safer):

-  Do not contain any reference to a global variable in the function
   body.

-  Do not contain any function call that is not unsequenced.

-  Do not contain any volatile lvalue.

The reason why the attribute is called 'unsequenced' is that such
functions can be reordered arbitrarily by the compiler, because they
only depend on the input.

[...]
> 
> If you have some references that would help me understand this stuff
> better, pitch 'em at me!

The formal specification of the attribute is pure crap; it's unreadable.
But the above constraints define it quite well.  Anyway, it's specified
here: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3783.pdf>.

> > It has issues, though, as it's too easy to misuse that attribute,
> > since the compiler will blindly trust anything you say.
> 
> This seems to be a recurring problem with attributes and, before them,
> type qualifiers in C.  See also "const" and "restrict".  Not too many
> days ago I saw reference to an old screed against "volatile" by Linus
> Torvalds.
> 
> For a language written primarily by people who know what they're doing,
> there sure do seem to be a lot of people who don't know what they're
> doing.  :-|
> 
> > [...]
> > > No proud C hacker ever lets correctness get in the way of
> > > performance.)
> > 
> > I am a proud C hacker, and I am proud to put correctness before
> > performance.  :)
> 
> And I applaud you for that.  I predict you'll get into a lot of fights
> with others who don't, but won't admit it.  "Number go up!"

I do.

> Maybe you can convince me to start saying "few" instead.  :D

I'm still part of a minority.  Here's part of draft of a proposal of
mine for fixing attributes.  I include a rant against a comment from
someone in the committee, which represents quite well the majority of
the committee.

        [[reproducible]] is unnecessarily dangerous.  The same or very
        similar optimization goals could be achieved without a complete
        and irresponsible disregard for safety.

        Moreover, there are proposals like n3499 which somehow creep in
        this dangerous feature in core parts of the language, which is
        even more worrying.

        We can add obvious constraints, to at least make this less of
        a weapon of mass-invocation of Nasal Demons.  This doesn't fully
        fix this attribute, and I plan to do more work, but this is the
        essential first step.

        As far as I can imagine, the one "benefit" of this lack of
        constraints is allowing programmers to override compiler
        analysis with functions that have debug code that violates the
        rules of the attribute.  For example, one may ++ a counter each
        time strlen(3) is called.  However, this is already possible in
        C by giving different information to different translation
        units.

                // f.h
                        [[reproducible]] int f(void);

                // f.c
                        // Do NOT include "f.h"

                        int
                        f(void)
                        {
                                static int  i;

                                return i++;
                        }

        This is a more idiomatic C way of lying to the compiler.  And it
        has the advantage that it's more explicit, so that the user has
        to opt in to the unsafety, instead of praying to $deity that it
        didn't accidentally write UB every time it uses an attribute.

        A member of the C Committee stated:

        > \* though personally I think QoI is satisfied by an
        > unconditional warning about use of these attributes;
        > safety-oriented users simply shouldn't be using this at all

        The committee is somehow acknowledging that attributes should
        be as dreaded as casts?!  Why would we standardize these
        features in the first place?  Our charter clearly talks against
        this.  We might as well introduce a new paragraph at the
        beginning of 6.7.12.1:

            6.7.12.1  Attributes :: Introduction
                @@ p0+1
                Attributes are extremely dangerous,
                and will likely result in
                all kinds of vulnerabilities in code that uses them.
                Do not use them in any code that cares about safety.
                They're only designed to make bogus code run faster,
                whatever that means.XXX)

                @@ New footnote
                XXX)
                The standard got rid of gets,
                so wg14 felt something similar should replace it,
                to keep the balance of the universe;
                the world needs a villan.

BTW, the comment of that member is something to be taken seriously,
as is the semi-kidding paragraph above.  There are two possible forward
paths:

-  I fix attributes, by passing a number of proposals, including this
   one.

-  Attributes are considered plutioniun.

There's no third way.  And so far, we're in the latter path.

[...]
> > > https://www.symas.com/post/the-sad-state-of-c-strings
> > > https://dgtalhaven.wordpress.com/2020/05/15/schlemiel-the-painters-algorithm/
> > 
> > In some cases, returning the input pointer is actually useful.  I use
> > the return value of memcpy(3) and strcpy(3) to construct interesting
> > one-liner macros.  Without that return value, it would be impossible.
> > 
> >     #define strndupa(s, n)  strncat(strcpy(alloca(n + 1), ""), s, n)
> > 
> > You might complain that this is inefficient (and indeed, you have a
> > link to Schlemiel the Painer above).  However, I'm proud to prefer
> > correctness over performance here.
> 
> Heh.  Fair point.  Let me pivot then.  I think the symmetry between
> fprintf and printf on the one hand (the latter is really just a
> specialization of the former to `FILE *stdio`) and sprintf on the other
> has proven to be deceptive and a trap, not just for the unwary C
> programmer, but for nearly all of us.
> 
> To see my point requires only one question to be asked and considered.
> 
> Why don't we have nprintf() and fnprintf()?
> 
[...]
> 
> Writing to memory buffers as such is fundamentally different from I/O.

Indeed.  It doesn't make sense to limit what's printed, because we don't
know the output device.

BTW, off-topic, but I do have an nprintf() function that is quite
different from that.  It calculates the length of the formatted string,
and is just

        #define nprintf(fmt, ...)  snprintf(NULL, 0, fmt, __VA_ARGS__)

> > > My explicit discards of the return value remind the reader (often
> > > myself) that, yes, I'm aware that printf() has a return value, and
> > > that I don't need it.
> > 
> > Are you sure?  I've seen snprintf(3) calls where you've discarded the
> > return value.  Should we conclude that you don't care about
> > truncation?  That's usually a quite bad bug.  What if some you thought
> > the buffer size would be enough but your calculation was wrong?
> 
> Me personally?  Or the groff code base in general?  I'm a very long way
> from replacing all of groff's C/C++ code with my own work.  That's not
> even a goal I have.  I select a task, try to constrain its scope, and
> try to confine my activities to resolution of that task.  Scope does
> frequently creep, but I try to (a) stage commits and (b) file Savannah
> tickets to log general clean-ups that I think should occur.

You personally.  By adding explicit casts, you've stated "I'm aware that
[this function] has a return value, and that I don't need it".  However,
now you claim to not have written that code, and thus not really be in
a position to do that claim.  It's contradicting information.  That's
why I think it would be more prudent to not make the claim.

> I admit that I don't maintain perfect discipline here, but I try.
> 
> > Should we conclude that you don't care about truncation?  That's
> > usually a quite bad bug.  What if some you thought the buffer size
> > would be enough but your calculation was wrong?  If you had added
> > error handling, you'd catch the bug pretty quickly.
> 
> I agree.  There is generally less error handling in groff than I would
> prefer.  One area I've devoted a lot of attention to is input validation
> in the formatter.  These additions have occasionally provoked unhappy
> responses from users who did not welcome their slop being pointed out to
> them.  "What do you MEAN my terminal has no 'C' font?!"  (Most, however,
> seem to adapt.)
> 
> > Alternatively, if you had used sprintf(3) and _FORTIFY_SOURCE, you'd
> > also catch the bug pretty quickly.
> 
> I frequently build groff with `-D_FORTIFY_SOURCE=2`, following a
> suggestion from Bjarni Ingi Gislason.  Frequently--and always before
> pushing.
> 
> > However, silent truncation will result in the bug surviving ages.
> 
> True, and if you know of any cases of string buffer truncation in the
> groff code base, I'd appreciate your telling this development community
> about them.

See below.

> 
> > > "But what if everybody did that?  You'd clutter the world with void
> > > typecasts!"
> > > 
> > > Yes, if I persist in using crappy APIs.
> > 
> > Are you saying snprintf(3) is a crappy API (and I do say that, FWIW,
> > but for other reasons)?  Returning a value is quite necessary.
> 
> See above regarding misleading similarity between fprintf and sprintf.
> 
> It was `printf()` that you raised in your original post.  ;-)

While the title mentions printf(3), I really meant printf(3) family,
which is what your commit says; it was an unfortunate and incorrect
abbreviation of the title.

Your commit *does* cover snprintf(3):

        $ git show 4b7a0fe5ab5d | grep snprintf
        -  snprintf(encoding, sz, "%s-%s", parsed.CharSetRegistry,
        +  (void) snprintf(encoding, sz, "%s-%s", parsed.CharSetRegistry,

[...]
> > > The standard C library was, and is, deserving of sterner scrutiny
> > > than it gets--and now that I mention it, gets() was far from the
> > > only grievous wart it has carried.  We have paid in confusion,
> > > wasted time, and unclear practices, and will continue to do so,
> > > unless we slaughter sacred cows and reconsider popular idioms from
> > > first principles.
> > 
> > I'm working on that.  :)
> 
> Full speed ahead!  :D
> 
> Regards,
> Branden
> 
[...]

Have a lovely night!
Alex

-- 
<https://www.alejandro-colomar.es>

Attachment: signature.asc
Description: PGP signature

Reply via email to