Warning: mysqli::__construct(): (HY000/1203): User howardkn already has more than 'max_user_connections' active connections in D:\Inetpub\vhosts\howardknight.net\al.howardknight.net\includes\artfuncs.php on line 21
Failed to connect to MySQL: (1203) User howardkn already has more than 'max_user_connections' active connections
Warning: mysqli::query(): Couldn't fetch mysqli in D:\Inetpub\vhosts\howardknight.net\al.howardknight.net\index.php on line 66
Article <vhah8t$3g67$1@dont-email.me>
Deutsch   English   Français   Italiano  
<vhah8t$3g67$1@dont-email.me>

View for Bookmarking (what is this?)
Look up another Usenet article

Path: ...!eternal-september.org!feeder2.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: David Brown <david.brown@hesbynett.no>
Newsgroups: comp.lang.c
Subject: Re: else ladders practice
Date: Sat, 16 Nov 2024 17:29:17 +0100
Organization: A noiseless patient Spider
Lines: 172
Message-ID: <vhah8t$3g67$1@dont-email.me>
References: <3deb64c5b0ee344acd9fbaea1002baf7302c1e8f@i2pn2.org>
 <vg358c$3bk7t$1@dont-email.me> <vg37nr$3bo0c$1@dont-email.me>
 <vg3b98$3cc8q$1@dont-email.me> <vg5351$3pada$1@dont-email.me>
 <vg62vg$3uv02$1@dont-email.me> <vgd3ro$2pvl4$1@paganini.bofh.team>
 <vgd6jh$1hmjc$1@dont-email.me> <vgds97$2r682$1@paganini.bofh.team>
 <vgdvfj$1m6ho$1@dont-email.me> <vgplgk$757j$1@paganini.bofh.team>
 <vgqk1h$edif$2@dont-email.me> <vgtkoi$igod$1@paganini.bofh.team>
 <vgv80r$1hcrf$1@dont-email.me> <vh8560$1n6hq$1@paganini.bofh.team>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 16 Nov 2024 17:29:18 +0100 (CET)
Injection-Info: dont-email.me; posting-host="e6529c30189e1b129c8bc4d9281312d3";
	logging-data="114887"; mail-complaints-to="abuse@eternal-september.org";	posting-account="U2FsdGVkX19M9U4VDzmhuh0QHiEqPa/O21s0uxAbElQ="
User-Agent: Mozilla Thunderbird
Cancel-Lock: sha1:et/oJKpMO5KvQFjcrtv6IoS4PrA=
Content-Language: en-GB
In-Reply-To: <vh8560$1n6hq$1@paganini.bofh.team>
Bytes: 11051

On 15/11/2024 19:50, Waldek Hebisch wrote:
> David Brown <david.brown@hesbynett.no> wrote:
>> On 11/11/2024 20:09, Waldek Hebisch wrote:
>>> David Brown <david.brown@hesbynett.no> wrote:
>>
>>> Concerning correct place for checks: one could argue that check
>>> should be close to place where the result of check matters, which
>>> frequently is in called function.
>>
>> No, there I disagree.  The correct place for the checks should be close
>> to where the error is, and that is in the /calling/ code.  If the called
>> function is correctly written, reviewed, tested, documented and
>> considered "finished", why would it be appropriate to add extra code to
>> that in order to test and debug some completely different part of the code?
>>
>> The place where the result of the check /really/ matters, is the calling
>> code.  And that is also the place where you can most easily find the
>> error, since the error is in the calling code, not the called function.
>> And it is most likely to be the code that you are working on at the time
>> - the called function is already written and tested.
>>
>>> And frequently check requires
>>> computation that is done by called function as part of normal
>>> processing, but would be extra code in the caller.
>>>
>>
>> It is more likely to be the opposite in practice.
>>
>> And for much of the time, the called function has no real practical way
>> to check the parameters anyway.  A function that takes a pointer
>> parameter - not an uncommon situation - generally has no way to check
>> the validity of the pointer.  It can't check that the pointer actually
>> points to useful source data or an appropriate place to store data.
>>
>> All it can do is check for a null pointer, which is usually a fairly
>> useless thing to do (unless the specifications for the function make the
>> pointer optional).  After all, on most (but not all) systems you already
>> have a "free" null pointer check - if the caller code has screwed up and
>> passed a null pointer when it should not have done, the program will
>> quickly crash when the pointer is used for access.  Many compilers
>> provide a way to annotate function declarations to say that a pointer
>> must not be null, and can then spot at least some such errors at compile
>> time.  And of course the calling code will very often be passing the
>> address of an object in the call - since that can't be null, a check in
>> the function is pointless.
> 
> Well, in a sense pointers are easy: if you do not play nasty tricks
> with casts then type checks do significant part of checking.  Of
> course, pointer may be uninitialized (but compiler warnings help a lot
> here), memory may be overwritten, etc.  But overwritten memory is
> rather special, if you checked that content of memory is correct,
> but it is overwritten after the check, then earlier check does not
> help.  Anyway, main point is ensuring that pointed to data satisfies
> expected conditions.
> 

That does not match reality.  Pointers are far and away the biggest 
source of errors in C code.  Use after free, buffer overflows, mixups of 
who "owns" the pointer - the scope for errors is boundless.  You are 
correct that type systems can catch many potential types of errors - 
unfortunately, people /do/ play nasty tricks with type checks. 
Conversions of pointer types are found all over the place in C 
programming, especially conversions back and forth with void* pointers.

All this means that invalid pointer parameters are very much a real 
issue - but are typically impossible to check in the called function.

The way you avoid getting errors in your pointers is being careful about 
having the right data in the first place, so you only call functions 
with valid parameters.  You do this by having careful control about the 
ownership and lifetime of pointers, and what they point to, keeping 
conventions in the names of your pointers and functions to indicate who 
owns what, and so on.  And you use sanitizers and similar tools during 
testing and debugging to distinguish between tests that worked by luck, 
and ones that worked reliably.  (And of course you may consider other 
languages than C that help you express your requirements in a clearer 
manner or with better automatic checking.)

Put the same effort and due diligence into the rest of your code, and 
suddenly you find your checks for other kinds of parameters in functions 
are irrelevant as you are now making sure you call functions with 
appropriate valid inputs.


>> Once you get to more complex data structures, the possibility for the
>> caller to check the parameters gets steadily less realistic.
>>
>> So now your practice of having functions "always" check their parameters
>> leaves the people writing calling code with a false sense of security -
>> usually you /don't/ check the parameters, you only ever do simple checks
>> that that called could (and should!) do if they were realistic.  You've
>> got the maintenance and cognitive overload of extra source code for your
>> various "asserts" and other check, regardless of any run-time costs
>> (which are often irrelevant, but occasionally very important).
>>
>>
>> You will note that much of this - for both sides of the argument - uses
>> words like "often", "generally" or "frequently".  It is important to
>> appreciate that programming spans a very wide range of situations, and I
>> don't want to be too categorical about things.  I have already said
>> there are situations when parameter checking in called functions can
>> make sense.  I've no doubt that for some people and some types of
>> coding, such cases are a lot more common than what I see in my coding.
>>
>> Note also that when you can use tools to automate checks, such as
>> "sanitize" options in compilers or different languages that have more
>> in-built checks, the balance differs.  You will generally pay a run-time
>> cost for those checks, but you don't have the same kind of source-level
>> costs - your code is still clean, clear, and amenable to correctness
>> checking, without hiding the functionality of the code in a mass of
>> unnecessary explicit checks.  This is particularly good for debugging,
>> and the run-time costs might not be important.  (But if run-time costs
>> are not important, there's a good chance that C is not the best language
>> to be using in the first place.)
> 
> Our experience differs.  As a silly example consider a parser
> which produces parse tree.  Caller is supposed to pass syntactically
> correct string as an argument.  However, checking syntactic corretnetness
> requires almost the same effort as producing parse tree, so it
> ususal that parser both checks correctness and produces the result.

The trick here is to avoid producing a syntactically invalid string in 
the first place.  Solve the issue at the point where there is a mistake 
in the code!

(If you are talking about a string that comes from outside the code in 
some way, then of course you need to check it - and if that is most 
conveniently done during the rest of parsing, then that is fair enough.)

> I have computations that are quite different than parsing but
> in some cases share the same characteristic: checking correctness of
> arguments requires complex computation similar to producing
> actual result.  More freqently, called routine can check various
> invariants which with high probablity can detect errors.  Doing
> the same check in caller is inpractical.

I think you are misunderstanding me - maybe I have been unclear.  I am 
saying that it is the /caller's/ responsibility to make sure that the 
parameters it passes are correct, not the /callee's/ responsibility. 
That does not mean that the caller has to add checks to get the 
parameters right - it means the caller has to use correct parameters.


Think of this like walking near a cliff-edge.  Checking parameters 
before the call is like having a barrier at the edge of the cliff.  My 
recommendation is that you know where the cliff edge is, and don't walk 
there.  Checking parameters in the called function is like having a 
crash mat at the bottom of the cliff for people who blindly walk off it.

> 
> Most of my coding is in different languages than C.  One of languages
> that I use essentially forces programmer to insert checks in
> some places.  For example unions are tagged and one can use
> specific variant only after checking that this is the current
> variant.  Similarly, fall-trough control structures may lead
> to type error at compile time.  But signalling error is considered
> type safe.  So code which checks for unhandled case and signals
> errors is accepted as type correct.  Unhandled cases frequently
> lead to type errors.  There is some overhead, but IMO it is accepable.
> The language in question is garbage collected, so many memory
> related problems go away.
> 
> Frequently checks come as natural byproduct of computations.  When
> handling tree like structures in C IME usualy simplest code code
> is reqursive with base case being the null pointer.  When base
> case should not occur we get check instead of computation.
> Skipping such checks also put cognitive load on the reader:
> normal pattern has corresponding case, so reader does not know
> if the case was ommited by accident or it can not occur.  Comment
> may clarify this, but error check is equally clear.
>