Path: csiph.com!eternal-september.org!feeder.eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail From: Keith Thompson Newsgroups: comp.lang.c Subject: Re: Jared Kushner would be proud... Date: Sun, 05 Apr 2020 02:33:46 -0700 Organization: None to speak of Lines: 138 Message-ID: <87eet2p98l.fsf@nosuchdomain.example.com> References: <4081ce0c-e0fb-4ada-b8b1-4fb97a1db723@googlegroups.com> <6c870890-6035-4272-bfcc-6d914ba969f3@googlegroups.com> <87r1x3ov2a.fsf@nosuchdomain.example.com> <3387ce6c-e0b7-4e05-9205-76126f18b5eb@googlegroups.com> <5f6047c2-d0a6-489c-9d45-a1db8cfb0150@googlegroups.com> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: reader02.eternal-september.org; posting-host="8b70dc153b1c96afc58cc48ef636d3fa"; logging-data="17538"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/QwLRq2hxLK+ILrtQst1lo" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) Cancel-Lock: sha1:4lgof0gGGLUD+NGx9AswOOPxpdc= sha1:FtkSZm2InuzzvNflJDlF/5A6Hns= Xref: csiph.com comp.lang.c:151055 Malcolm McLean writes: > On Saturday, 4 April 2020 23:46:55 UTC+1, Richard Damon wrote: >> On 4/4/20 4:52 PM, Malcolm McLean wrote: >> > On Saturday, 4 April 2020 21:27:54 UTC+1, Keith Thompson wrote: >> >> Malcolm McLean writes: >> >> [...] >> >>> It also implies discussion about fine, almost meaningless points. >> >>> For instance, since the standard uses the term "array of character type" >> >>> (and defines "character type", which I wasn't aware of) what is the >> >>> status of this program? >> >>> >> >>> #include >> >>> >> >>> int main(void) >> >>> { >> >>> printf("***%.*s***\n", 0, (void *) 0); >> >>> >> >>> return 0; >> >>> } >> >>> >> >>> But is there actually an answer in the standard, as opposed to a >> >>> knowledge of how C implementations work? >> >> >> >> I don't know why you think this is a good example of whatever >> >> point you're trying to make. Yes, there is actually an answer in >> >> the standard. The description of the "s" conversion specifier says: >> >> >> >> If no l length modifier is present, the argument shall be a >> >> pointer to the initial element of an array of character type. >> >> >> >> (void*)0, even if interpreted as a value of type char*, clearly is >> >> not a pointer to the initial element of an array. The violation >> >> of a "shall" outside a constraint means the program has undefined >> >> behavior. >> >> >> >> It's likely that an actual implementation would not dereference >> >> the pointer and the program's output would be "******" followed >> >> by a newline. That is, as you know, entirely consistent with >> >> undefined behavior. >> >> >> >> This is why you shouldn't pass a null pointer corresponding to a >> >> "%...s" directive (where "..." is any, possibly empty, sequence of >> >> characters that are valid in that context). >> >> >> >> The standard *could* have said that it's permissible to pass a null >> >> pointer if none of the characters are used, but that would have >> >> made the standard more complicated with no real benefit. As it is, >> >> an implementation that calls abort() if it's passed a null pointer >> >> in that context could be conforming. >> >> >> >> What was your point again? >> > >> > Are you sure the implementation is allowed to call abort() if the char * >> > argument is null and the character count is zero? Or did you just take >> > that decision on your own authority? >> > >> > I'm not saying you are wrong. I'm saying it's a marginal case that isn't >> > covered, as far as I can tell. >> >> Since the Standard (explicitly) says that it is undefined behavior if >> the argument is not a pointer to the initial element of an array (as the >> NULL pointer doesn't point to any object), then yes, the Standard is >> clear on this. And I could actually see a library written defensively >> (trying to catch as many undefined behaviors and turning them into some >> explicit error condition rather than doing some critical undefined >> behavior), catching this, as at the begining of the %s processing, it >> might have an assert that the pointer is not null, before checking if >> the max width was set to 0. >> > It's worse than that, and Keith's inerpretation is right. Thank you, but I don't see what's "worse" about it. > The relevant section is actually buried away soemwhere in the preamble > to "functions" where it says that a pinter shall be "valid", and null > is not "valid". Even in N is 0. The relevant section is the one I cited, the one that says "the argument shall be a pointer to the initial element of an array of character type". I think you're referring to 7.1.4 (Use of library functions) which says that if an argument to a function (here it's referring to library functions, not to user-defined functions) has an invalid value the behavior is undefined. But there's no need to resort to that, since the undefinedness of passing a null pointer to printf is specified in the description of printf itself. (Actually fprintf; printf is defined in terms of fprintf. And actually just in this particular context; printf("%p", (void*)0) is perfectly valid). > So this is a disaster. memcpy() does not handle the null case properly. How is that a disaster? Just don't call memcpy with a null pointer. In fact it's not possible in general for memcpy to determine whether a pointer passed to it is valid. It might be uninitialized, or it might point to memory that's been deallocated, or it might just point to the wrong object. The standard *could* have required some kind of checking for null pointers (which would do what if the check fails?), but since invalid pointers in general are not detectable, there wouldn't have been much point. > Consider. It is very likely that in real code, N can be zero, but such > cases are exceptional and very rare. > It is also likely that the buffers are allocated by a call to malloc(). > It is also quite likely that malloc(0) returns non-null. > > It is also quite likely that on the platform the code is tested on, > memcpy() does handle memcpy(0, 0, 0) as a no-op. > > So this is asking for bugs to slip through and only be detected at > a late stage. The fact that malloc(0) might return either a null pointer or a valid pointer that can't be safely dereferenced is, I'll agree, a bit of a problem. I think it's defined that way because existing pre-standard implementations behaved both ways. https://www.lysator.liu.se/c/rat/d10.html#4-10-3 But really, it's not that big a deal. Don't assume that malloc(0) will return a non-null pointer, don't assume malloc() always succeeds, and don't pass null pointers to functions that aren't prepared for them. You can certainly shoot yourself in the foot with C. You seem to be complaining that it doesn't provide Kevlar over your left big toenail. The trick is to avoid shooting yourself in the foot in the first place, by understanding the rules of the gun and being careful. (And yes, it is tricky.) Have you run into the problems you're worried about in real code? Have you actually written something similar to printf("***%.*s***\n", 0, (void *) 0); It looks to me like a contrived example, one that you presented as a "meaningless point". -- Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com Working, but not speaking, for Philips Healthcare void Void(void) { Void(); } /* The recursive call of the void */