Search code examples
cposixglibc

Can we add attributes to standard function declarations without breaking standard?


I wonder if adding attributes to a C method will break standard compliance or not. Because in glibc 2.38, a nonnull attribute is added to fclose() and freopen() like this:

extern int fclose (FILE *__stream) __nonnull ((1));

In C++, this change is a headache as well because all following lines creates a warning now:

std::unique_ptr<FILE, decltype(&fclose)> fptr(fp, fclose);

But there is no such attribute in POSIX standard. I wonder if adding this attribute by glibc breaks POSIX compliance. Any idea?


Solution

  • When compiling with gcc or g++ (or clang/clang++), the new declaration with the __nonnull macro in glibc 2.38 is expanded to

    extern int fclose (FILE *__stream) __attribute__ ((__nonnull__ (1)));
    

    These compilers can handle the non-standard __attribute__ ((__nonnull__ (1))). For a compiler that can't handle that attribute, __nonnull ultimately expands to nothing.

    However, when compiling this line

    std::unique_ptr<FILE, decltype(&fclose)> fptr(fp, fclose);
    

    then g++ emits this warning:

     warning: ignoring attributes on template argument ‘int (*)(FILE*)’ [-Wignored-attributes]
    std::unique_ptr<FILE, decltype(&fclose)> fptr(fp, fclose);
    

    and you'll get the same warning even if you package fclose in a function where you provide the same attribute using the C++ [[attribute]] syntax on a free function:

    [[gnu::nonnull(1)]] int closer(std::FILE* fp) {
               // (1) may be omitted since there's only one argument to the function
        return std::fclose(fp);
    }
    

    Both gcc and clang have bug reports about this, but fixing it would involve an ABI addition "Due to the way templates cause name managling to come into play", so the attribute is dropped and is not part of the Deleter type. The gcc report is in status SUSPENDED.

    Since std::fclose is not on the list of designated addressable functions, you should never take its address directly. Either you put the std::fclose call in a function like above and live with the warning - or package the call in a functor

    like a lambda:

    auto closer = [] (std::FILE* fp) [[gnu::nonnull]] { std::fclose(fp); };
    std::unique_ptr<std::FILE, decltype(closer)> fptr(fp, closer); 
    

    or in a user-defined type:

    struct closer {
        [[gnu::nonnull]] void operator()(std::FILE* fp) const {
            std::fclose(fp);
        }
    };
    // ...
    std::unique_ptr<std::FILE, closer> fptr(fp);
    

    and with that, the warning goes away since the attribute is not on the type supplied but on the call operator.

    Of course, if you are willing to skip the potential optimization given by supplying [[gnu::nonnull]] you can just remove it from the free closer function above and use that to avoid the warning.