bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: restrict


From: Bruno Haible
Subject: Re: restrict
Date: Mon, 17 Feb 2020 00:49:52 +0100
User-agent: KMail/5.1.3 (Linux/4.4.0-171-generic; KDE/5.18.0; x86_64; ; )

Hi Paul,

Thanks a lot for your comments.

> >    - If all pointer parameters point to different types, excluding 'void *',
> >      ISO C forbids aliasing anyway, therefore 'restrict' is redundant.
> 
> The actual ISO C rule is a bit different, since it allows aliasing between 
> 'char 
> *', 'unsigned char *', 'signed char *', and the other types.  This means one 
> must use 'restrict' when some pointers are 'char *' and others are not. For 
> example:
> 
>    FILE *freopen(const char *restrict pathname, const char *restrict mode,
>         FILE *restrict stream);
> 
> The same rule applies when a type *might* cause problems. For example:
> 
>    int readdir_r(DIR *restrict dirp, struct dirent *restrict entry,
>         struct dirent **restrict result);
> 
> because DIR might be either 'char' or 'struct dirent' (weird, but allowable).

This is true, when someone wants to use 'restrict' for optimization, to save
a machine instruction here and there.

I'm after warnings that point to undefined behaviour, and since passing a
'char *' in place of an 'unsigned char *', 'FILE *', or similar already
generates a compiler warning, using 'restrict' here does not offer an
advantage.

Example:
================================
#include <stdio.h>
char *str;
int main ()
{
  freopen (str, "w", str);
}
================================
produces (with "gcc -Wall"):

foo.c: In function ‘main’:
foo.c:5:22: warning: passing argument 3 of ‘freopen’ from incompatible pointer 
type [-Wincompatible-pointer-types]

> >   - If, among the parameters, there are N parameters of type
> >       [const] TYPE *
> >     adding the 'restrict' keyword to N-1 of them is equivalent to adding
> >     the 'restrict' keyword to all N of them.
> 
> It won't be equivalent in general, because if the function accesses nonlocal 
> variables the compiler can't be sure they won't alias with the Nth parameter. 
> We 
> should add 'restrict' to all N args, as POSIX does.

Again, this is true for optimization purposes, but not needed for warning
purposes. In functions with variadic arguments, for example

  int snprintf (char *str, size_t size, const char *format, ...);

we can express the fact the 4th, 5th, etc. arguments should not overlap
with the first argument *only* by adding 'restrict' to the first parameter.
And since 'restrict' on the first parameter is going to signal a warning
for overlaps with the 3rd, 4th, 5th, ... parameter, we don't need 'restrict'
on the 3rd parameter.

> >      Example: void dfacomp (char const *, ptrdiff_t, struct dfa *, bool);
> >      and 
> > https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html
> 
> In glob.html, 'glob' has 'restrict' on both data pointer args, so it's not an 
> example of this principle.

But for the caller, it does not make a difference whether 'glob' is declared
as
      int glob (const char *restrict pattern, ..., glob_t *restrict pglob);
or as
      int glob (const char *         pattern, ..., glob_t *restrict pglob);

If the implementation of 'glob' is writing to a global variable, and I happen
to pass a pointer to this variable as the first argument - this is the case
which could produce undefined behaviour -, the compiler cannot give me a
warning about it because it's not written in the glob.h file which global
variables the function may access. So, for warning purposes, 'restrict'
on the first parameter is redundant.

> >    - If a function has an input parameter of type
> >        const TYPE *
> >      and an output parameter of type
> >        TYPE *
> >      then the 'restrict' keyword means that the implementation may write
> >      the output before having finished reading all the input.
> >      Example:
> >        void arctwo_encrypt (arctwo_context *context, const char *inbuf,
> >                             char * restrict outbuf, size_t length);
> >      Whereas the absence of the 'restrict' keyword means that the
> >      implementation will read all the input _before_ starting to store
> >      the output.
> >      Example:
> >        int hmac_md5 (const void *key, size_t keylen,
> >                      const void *buffer, size_t buflen, void *resbuf);
> 
> Yes and no. By "the implementation" do you mean the C code that implements 
> these 
> functions with the usual naive interpretation of that C code? or do you mean 
> the 
> optimized version of the C code? If the latter, I don't think I agree. If the 
> former, I'm not sure -- but why focus on the former?

I mean any possible implementation, compiled from the code we have in gnulib.
'restrict' is part of the contract between the caller of the function and the
implementation of the function. When the compiler optimizes the implementation,
it has to stick to the contract.

> Anyway, for hmac_md5 I would think 'restrict' would also be appropriate, as 
> we 
> don't want to preclude an implementation that double-checks the result.

Excellent point! If a function is declared with 'restrict', the implementor
may add debugging code at the end, e.g. that prints the values of the arguments.
Whereas if a function is declared without 'restrict', the caller may pass
pointers to overlapping memory areas, thus such debugging code would not work.

> >    - 'restrict' should NOT be used for multiple output parameters of the
> >      same type, when only a single word is written through each parameter.
> >      Example: ssize_t copy_file_range (int ifd, off_t *ipos, int ofd, off_t 
> > *opos,
> >                                        size_t len, unsigned flags);
> >      Rationale: It is OK to have ipos and opos point to different elements
> >      of the same off_t[] array.
> 
> Ouch, this is exactly the opposite of what I would have wanted, since I want 
> 'restrict' to tell the caller "these should not be aliases" and I want that 
> to 
> be true for *IPOS and *OPOS.  (I'm not sure what is meant by 'single word' 
> here; 
> surely it shouldn't matter whether the type is a struct....) Could you 
> explain 
> more where the 'same array' rule comes from?

The "same array" rule comes from what I understood from various explanations
of 'restrict' on the web [1], and from what I see in the glibc headers for
functions that have multiple output parameters of the same type:
  - splice in <bits/fcntl-linux.h>,
  - printf_arginfo_size_function in <printf.h>,
  - openpty in <pty.h>,
  - re_set_registers in <regex.h>,
  - copy_file_range in <unistd.h>.
But ecvt, fcvt in <stdlib.h> have 'restrict'. You may want to register a glibc
bug if you think these five functions should have 'restrict' :)

Bruno

[1] 
https://stackoverflow.com/questions/745870/realistic-usage-of-the-c99-restrict-keyword




reply via email to

[Prev in Thread] Current Thread [Next in Thread]