Skip to content

Const Correctness

Y-Less edited this page Dec 6, 2018 · 3 revisions

Const Correctness

Compiler version 3.10.9 introduced a new warning related to the correct use of const in function declarations and definitions:

warning 239: literal array/string passed to a non-const parameter

Given that this new warning reveals previously hidden semantics, users may be confused when seeing this warning appear on their code for the first time and this document aims to explain what const correctness is, why it exists and how you can write better code!

Pass-By Semantics

Before diving into what const means in function declarations/definitions, you should have a good understanding of the different ways arguments are passed to functions.

Pass-By-Value

When you pass normal variables to a function, they are passed by value:

main() {
    new a; // a is allocated here
    f(a); // a is passed to f by value
}

f(b) {
    // b is a copy of a
    // modifying b will not modify a
}

Pass-By-Reference

And, you can add an ampersand to tell the compiler to pass by reference:

main() {
    new a; // a is allocated here
    f(a); // a is passed to f by value
    // since f modified its parameter
    // a is now = 3
}

f(&b) {
    // b is a reference to a
    // modifying b will also modify a
    b = 3;
}

Arrays and Pass-By-Reference

Arrays are always pass by reference. The reason for this is out of scope for this guide but you can read about why C does this and it will apply to Pawn.

This is why SA:MP functions like GetPlayerName work, it expects a reference to an array large enough to hold a player name and writes the name into the array. It doesn't need to return the name because it simply modifies the input array.

main() {
    new a[4];
    GetHi(a);
    // a is now "hi!" with a zero-terminator
}

GetHi(input[]) {
    input[0] = 'h';
    input[1] = 'i';
    input[2] = '!';
    input[3] = '\0';
}

So we know that GetHi modifies input by writing characters into each cell. But what would happen if we passed a string literal to this function?

main() {
    GetHi("123");
}

GetHi(input[]) {
    input[0] = 'h';
    input[1] = 'i';
    input[2] = '!';
    input[3] = '\0';
}

This is undefined behaviour and, in the previous compiler, would go completely un-noticed because there was no warning to tell the developer they made a mistake by passing a string literal to a function that may attempt to modify it.

Const Semantics

This is where const comes in. I want to emphasise that this is not a new feature at all, the only new feature is the addition of a warning that will point out functions that should either use const or have string literals (or const arrays) being passed to functions that may attempt to modify them.

Here's a function that does not modify the input, it merely prints it:

main() {
    Say("Hi!");
}

Say(input[]) {
    if(input[0] == '\0') {
        return;
    }
    printf("I say: %s", input);
    return;
}

This function may look logically correct, and it is: it checks if the string is null and returns if so, if not, it prints what it needs to and exits. There's nothing wrong with the logic of this function.

However, the semantics of the function's signature* are incorrect. The incorrectness comes from the fact that if you just read the function's signature:

Say(input[])

You assume the following facts:

  • The function is called Say
  • It takes a single array as a parameter
  • It may modify the parameter

That last point is the source of the incorrectness. There is nothing in this function's signature to suggest that it does not modify the input.

How do we fix this? const!

Say(const input[]) {
    if(input[0] == '\0') {
        return;
    }
    printf("I say: %s", input);
    return;
}

You now know for certain that it is safe to pass a const array or a string literal to this function, assured that it can not under any (valid) circumstances modify it.

*signature: the combination of a function's qualifier, input types, input qualifiers and return type

Conclusion

Hopefully this guide was useful and you may be discovering bugs in your code already. It may seem like the compiler is being overly strict but these issues can cause serious flaws resulting in very difficult to debug issues such as Invalid instruction runtime errors.