• dpm (unregistered)

    My first concern is why "snprintf() and open() and write() and close()" instead of simply "fopen() and fprintf() and fclose()".

  • Argle (unregistered)

    I programmed almost exclusively in C between 1984 and 1990. I saw a lot of that &buf[0] thing back then. I lump it in with things like "if ( a == true )" or using pointers in C++ when a reference is what you want. They are things that work versus best practices and actual understanding.

  • (nodebb)

    Arrays in C are pointers to the first object in the array

    No.

    They are not pointers to the first object in the array. The name of the array, if interpreted, in and of itself, as a value,"returns" a pointer to the first object in the array, but that pointer can be found exactly nowhere in the array variable.

  • Michael R (unregistered) in reply to dpm

    You must be new here.

  • Hanzito (unregistered)

    Oh dear Lord, is this one of those posts where people are going to be more pedantic than Steve the Cynic?

    "Actually, in ANSI C, ..."

    "Do you mean C99 or any of the ISO/IEC 9899?"

    "Arrays don't have a name. A variable is ..."

  • HL (unregistered)

    Probably just me, but when given a choice between "implicitly refer to the array as the first element" and "explicitly refer to the first element"; I would go for explicitness every damn time.

  • (author) in reply to HL

    But their goal isn't to get the first element- their goal is to access the array pointer itself. snprintf takes an array pointer, which by specification is a pointer to the first element, but we expect snprintf to operate on the entire array contents, not the first element alone.

  • Anonymous') OR 1=1; DROP TABLE wtf; -- (unregistered) in reply to Remy Porter

    Technically no, snprintf takes a "pointer to character", aka char*, which is distinct from "pointer to array", which would be something like char (*)[100].

    Arrays decay into pointer to their first elements in many contexts, but they are not exactly the same thing.

    char buf[100];  // Type: "array 100 of char"
    snprintf(buf, ...);  // Array decays to &x[0], causing implicit conversion from "array 100 of char" to "pointer to char"
    snprintf(&buf[0], ...);  // No implicit conversion needed, function expects "pointer to char" and received "pointer to char"
    
    char (*p)[100] = &buf;  // No decay: type is "pointer to array 100 of char"
    (*p)[3] = 'A';
    snprintf(p, ...);  // Error: cannot convert from "pointer to array 100 of char" to "pointer to char" (actually a warning though instead of error, since all pointer types are implicitly convertible to char* in C [but not in C++], but it's still a coding error)
    snprintf(*p, ...);  // OK, *p is "array 100 of char" and decays into a pointer to its first element
    

    The technical details of arrays are messy in C/C++.

  • (nodebb) in reply to HL

    Basically, the reason that arrays decay to a pointer to the first element is precisely so you don't have to write that verbose &array[0] expression all the time. It's technically correct, but it's not idiomatic.

    I cringe similarly when I see code like *(array+i) instead of array[i].

  • (nodebb)

    O_CREAT without the access right bits, sounds ominous.

  • (nodebb) in reply to Medinoc

    Without the access bits, it uses defaults. Given that it's into /tmp which has some special flags set anyway, that's not a problem.

    Not using fopen() and fprintf() for this sort of task though... not great. Not checking for errors properly... very not great.

  • Tim (unregistered)

    TRWTF is C.

    For proof: (a) it would be impossible to have the pedantic discussion above about any sensible high-level language. (b) writing a single line to a file (and throwing an exception if there was any error doing so) should be a one-liner

  • (nodebb)

    Along with everything else that everyone else is wondering about, I wonder why they have the 10x retry feature. Yes, it's badly implemented with no waits, no error handling, etc. But why does it exist at all?

    Some kind of work-around for a badly handled multithreading issue in this app? A race condition with whatever other app is reading and then deleting /tmp/conf? Boilerplate mindless added for ... mindless reasons?

    Whatever the reason, it doesn't do it for me.

  • (nodebb)

    If I recall correctly, in STL you could get the address of a vector by doing &myVec[0] and then treat it as a C-style array. Maybe that knowledge spilled over here.

  • Andreas (unregistered) in reply to dkf

    There are no default arguments in C. If you omit the third argument in an open(...,...|O_CREAT) call, you get undefined behaviour because the function tries to access a parameter that is uninitialized (if the file needs to be created).

  • (nodebb) in reply to Hanzito

    Except ignoring the pedantry can bite you on the arse. For example

    char buf[100];
    char *p = buf; 
    
    if (sizeof buf == sizeof p)
    {
        printf("That's a mighty big pointer type you've got there\n"");
    }
    

    There's a whole class of Stack Overflow questions that stems from the confusion of what sizeof array and sizeof pointer` return.

  • Watts (unregistered)

    I’m sure someone will correct me if I’m wrong, but doesn’t snprintf not take in to account the trailing null on the string? So using sizeof(buf) as the size limit might write a null to the next byte after buf?

  • Klimax (unregistered) in reply to davethepirate

    You remember correctly. It was in use before data() was introduced. IIRC between C++98 and C++03.

  • (nodebb)

    I do not believe this is quite the WTF it's being presented as as I have had occasion where not quite so crazy code was actually needed.

    There's a consumer out there that's going to read the file. If the consumer is currently reading the file the write is going to fail. However, if the consumer simply quickly reads and then closes the retries on the write here make sense--it's looping long enough that if the consumer is working as intended the write will go through (and why we are referencing a global here--this is what I consider a only mildly evil use of globals: configuration information read at startup and never written again. Sure, gathering them up into a structure makes unit testing a lot easier, but otherwise it's not an issue) but if the file is truly locked this will not hang.

    My case involved a program that knew nothing of networks and thought it was running on an ordinary PC. If I was quick enough about it I could replace it's entire state save without it noticing--the locked read got a couple of retries before failing and so long as I let go fast enough it would never have a problem. Said program wasn't ours, it couldn't be taught modern concepts.

    As for the buffer--while the reference is obviously wrong the rest could be a dingbat consumer issue--it's expecting the text to be padded to some standard size. It reads X bytes rather than to EOF. Again, this could easily be messing with the internals of some other program.

  • L (unregistered) in reply to Barry Margolin

    i[array]

  • Worf (unregistered)

    The reason arrays decay is pointer arithmetic.

    Canonically, if x is an array of say, 10 things, x[0] is syntatically identical to *(x+0). Thus, you can abuse things somewhat in C where instead of say x[5], you can do 5[x] instead - this is because x[5] is *(x+5) which can be rewritten as *(5+x), or 5[x].

    &x[0] is equivalent to &(*(x+0)), so you're getting X, adding 0, dereferencing the pointer to get at the item, then getting the address of the item back.

    Of course, in C++ this nonsense doesn't exist and is a syntax error since C++ expects an array object that can take the [] operator.

  • Duke of New York (unregistered) in reply to Tim

    Everything is a one liner, in any programming language, if it has been coded as a subroutine in advance. There is always a point at which the developer has to take up where the library leaves off.

  • Argle (unregistered) in reply to Barry Margolin

    Along your line of comment, I always love asking C programmers if the following will compile:

    char c = 2["Hello!"];

    The vast majority say that it won't compile or that it needs to be in a C++ program.

  • (nodebb) in reply to Argle

    I'm afraid to ask, but what the hell would that do? I'm guessing nothing that won't summon nasal demons.

  • (nodebb) in reply to Argle

    I'm afraid to ask, but what the hell would that do? I'm guessing nothing that won't summon nasal demons.

  • jd (unregistered) in reply to prueg

    It's the same as writing "Hello!"[2].

  • (nodebb)

    len=write(fd, &buf[0], strlen(buf));

    Why capture the length written if not going to use it all?

    That bugs me. Maybe there was a print or since check, and it got removed? Or maybe that was copied from another place that was checking the len? Or maybe the writer intended to and then forgot?

    Also why not break the 10 iteration loop on success? If this can write the file, it's going to write it 10 times? This means it could be possible that 9 writes succeed but the 10th one opens, thus erasing the previously properly written file, and fails silently.

  • (nodebb) in reply to jd

    It's the same as writing "Hello!"[2].

    Which in turn is exactly the same as writing 'l'

  • (nodebb)

    why use &buf[0] ?

    There's some logic behind this : if someone is quickly scanning some code, this syntax makes it obvious we're dealing with the address of the first element of an array. Whereas quickly scanning "buf" requires one to check what is buf and how it is declared.

    Now don't kill the messager, I don't necessarily subscribe to that school but there is some argument there, especially for non string arrays.

    That said for a string buffer, a printf(buf) is still IMHO the most readable syntax. I personally don't consider a char* to be an "array" but semantically to be a pointer on a string buffer, thus making that &buf[0] nonsense pointless. Just a question of semantics obviously. Ymmv.

  • TheCPUWizard (unregistered)

    All depends on intent.... "fixed" or "flexible".... using "&buf[0]" and "buf" are the same for an array (in this case of char)... but what if the declaration of buf changes but the usage would e semantically valid...

    auto thing = GetThing(); thing.TurnOn();

    if thing is currently a light switch, but later refactored to a nuclear bomb -- shrug...

    [personally I prefer explicit and strict typing]

  • (nodebb)

    @Ralf ref

    Also why not break the 10 iteration loop on success? If this can write the file, it's going to write it 10 times? This means it could be possible that 9 writes succeed but the 10th one opens, thus erasing the previously properly written file, and fails silently.

    I see a break; on success. Do you?

  • akozakie (unregistered) in reply to Remy Porter

    Exactly! When I wrote C, I'd use buf to pass the pointer to the whole array for modification, and &buf[0] to pass the pointer to the first element for modification. It's the exact same value, but the difference in notation makes the intent clearer. So, using &buf[0] to pass the array pointer just... hurts my eyes.

  • Guessed (unregistered) in reply to Steve_The_Cynic
    Comment held for moderation.
  • Anonymous') OR 1=1; DROP TABLE wtf; -- (unregistered) in reply to Ralf

    That said for a string buffer, a printf(buf) is still IMHO the most readable syntax.

    Please don't do that, that's an immediate security issue. Use printf("%s", buf) instead.

  • Don (unregistered) in reply to HL

    I've been a C programmer for over 20 years and I use the exact syntax as presented here.

    1. It's more clear since you're dealing with an array (char c[100]), not a pointer (char *p).
    2. It eliminates compiler warnings on many compilers.
    3. One point hinted at in the comments also, it makes it more clear that you can use sifeof(c) as another parameter to the function call.
    4. Also clear that the storage is on the local stack and did not come as a parameter to the function you're in (where it would often be char *c).
    5. Probably others I can't think of now.

    As for if ( a == true ). Don't like exactly that syntax but... if ( ( flag & FOO_FL_CREAT ) == 0 ) if ( ( flag & FOO_FL_CREAT ) != 0 ) are good and clear ways to check a bit, even if the == 0 syntax is unnecessary. As an exaple, it allows you to check several bits in one test using the same syntax.

    I feel in C it's always best to be as clear as possible. It reduces bugs. Don't do "smart" language tricks just because you can. In the end, it's just a style that sticks when you find the reasons, reasons for you, to stick with it.

  • jes (unregistered)

    My guess is the program is running under systemd and has its own fake /tmp.

    Also:

    There's a consumer out there that's going to read the file. If the consumer is currently reading the file the write is going to fail.

    Nope, the write will still succeed.

  • Andrew Klossner (unregistered) in reply to Ralf
    Comment held for moderation.
  • Man with One Red Shoe (unregistered)
    Comment held for moderation.
  • Man with One Red Shoe (unregistered)
    Comment held for moderation.

Leave a comment on “Write, Write Again”

Log In or post as a guest

Replying to comment #:

« Return to Article