3

To my understanding, standards are mainly for code portability between compilers, but I'm curious if some things in the standard that are only treated as warnings when not followed can cause software to actually fail, or if code is written that does not comply with the standard, and it compiles/executes where it needs to, can it usually be left alone?

Here's an example from the book Deep C Secrets (explanation about why that happens will be below everything). I'm not sure if this specific issue could cause possible mayhem (though it's an implicit cast to otherwise incompatible types), but it's the example that brought the question to my mind.

void foo (const char ** p) {}

int main (int argc, char ** argv)
{
    foo(argv);   //compiler says "line 5: warning: argument is incompatible with prototype"
    return 0;
}

For the sake of the argument let's assume that the developer says "no big deal" and decides not to fix the warning since the code compiles (on their compiler). If this, hypothetically, could cause software to break why is it not an error? Or is a warning simply brought up because some other compiler may not support the assignment of const char ** = char **?


why it happens (or just check C FAQ):

On page 92 of this standard, the third possible constraint under (1) for simple assignments is that:

  • both operands are pointers to compatible types (with or without qualifications)
  • the type pointed to by the left operand has all the qualifiers (or more) than the type pointed to by the right operand

In this case, char ** and const char ** are both pointers to different unqualified types (pointers, one to a const char * and, a other to a char *).

1
  • It can­​­​­​­​­​
    – user348453
    May 21, 2020 at 5:28

2 Answers 2

8

To my understanding, standards are mainly for code portability between compilers

No, a programming language is a specification (written in some report in English with some formalizations), and that specification is (often) the standard. Some implementations (notably GCC compilers) accept (and document) useful extensions to the standard (such as statement-exprs & labels-as-values, and both are accepted by Clang too).

BTW ANSI C, a.k.a. C89, is an obsolete standard. You should follow C99 or perhaps C11.

Your compiler is right to warn you. And there is a simple fix: add a explicit cast for readability:

foo((const char**)argv);

the generated machine code is likely to be the same (on usual desktops or laptops), but the improved code is more readable (to you, or to the developer working on the same code base).

Notice that on some (weird) computers, a const data might be manipulated differently than a non-const one. For example, it might sit in some different address space, or different kind of memory (ROM vs RAM), or loaded with different instructions (e.g. on some Harvard architectures, where const data would sit in the instruction space).

Also const and restrict could enable more optimizations by the compiler (e.g. caching some loaded value in a register and reusing it more), so the generated code would be (sometimes) different, e.g. inside a more complex foo function.

A difficult notion when programming in C and in C++ is that of undefined behavior. Read Lattner's blog: What Every C Programmer Should Know About Undefined Behavior

I recommend to enable all warnings in your compiler (e.g. gcc -Wall -Wextra and perhaps even -Werror) and to improve the source code till you get no warnings. You'll gain a lot of developer's time following such discipline.

0
3

This particular warning is there for a serious reason: That kind of code opens you up to nasty bugs.

If you pass a char* to a function that expects a const char*, that's fine. It just means that the compiler won't allow that function to modify the chars pointed to, even though they may be really modifiable and the compiler would have allowed the calling function to modify them. No harm done.

You pass a char** to a function that expects a const char**. You are passing a pointer to a function expection a pointer. The calling function expects char* to be stored via that pointer. The called function expects const char* to be stored via the pointer. Therefore, the called function will let you store a const char*, which points to chars that must not be modified. The calling function thinks the pointers stored are char*, so it will think it's fine to modify the chars pointed to.

All in all, this call builds a trap that lets you modify const chars without any cast.

static const char c = 'c';
void caller (void) {
    char d = 'd';
    char* p = &d;
    called (&p);
    *p = 'e';
}
void called (const char** q) {
    *q = &c;
}

Function called () sets p to &c, that is a const char*. Then p, that is the const char c, is set to 'e' - undefined behaviour, likely a crash or worse.

2
  • "That kind of code opens you up to nasty bugs" - so, why isn't this warning fatal?
    – galois
    Jul 31, 2016 at 14:57
  • 2
    Because you might never store a const char*. Most importantly, there are compiler options that make any warning fatal. Many developers use them.
    – gnasher729
    Jul 31, 2016 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.