-
Notifications
You must be signed in to change notification settings - Fork 72
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!
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.
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
}
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 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.
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
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.