Liste des Groupes | Revenir à cl c |
David Brown <david.brown@hesbynett.no> wrote: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.On 11/11/2024 20:09, Waldek Hebisch wrote:Well, in a sense pointers are easy: if you do not play nasty tricksDavid 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.
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.
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!Once you get to more complex data structures, the possibility for theOur experience differs. As a silly example consider a parser
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.)
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.
I have computations that are quite different than parsing butI 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.
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.
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.
Les messages affichés proviennent d'usenet.