This site: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html says that it is defined for unsigned integers. Will using it for signed int give wrong results in some cases or not?
-
Maybe. If it says it is only defined for unsigned, signed may work or not. Don´t rely on it. – deviantfan Apr 26 '15 at 15:04
-
Why not just cast to `unsigned int` (an operation which is completely well-defined by the language) before calling it? Do you intend to pass negative numbers, and if so what answer do you want? – Alan Stokes Apr 26 '15 at 15:23
3 Answers
__builtin_popcount
is a gcc-specific extension. It acts like a function with the declaration:
int __builtin_popcount (unsigned int x);
If you had an actual function with that declaration, and its declaration were visible, then you could pass an argument of any numeric type to it. Since the declaration is a prototype, any argument you pass would be implicitly converted to the parameter type, unsigned int
.
Conversion from (signed) int
to unsigned int
is well defined. If the value being converted is within the range 0
.. INT_MAX
, the value is unchanged. Otherwise, it's wrapped module UINT_MAX+1
. For example, converting -1
to unsigned int
yields UINT_MAX
, which is 232-1 if unsigned int
is 32 bits wide.
So the question is, does gcc treat __builtin_popcount
as a function with a visible prototype? Since it's a language extension, it doesn't have to, and the gcc manual isn't entirely clear. It shows a prototype for it, but that doesn't necessarily mean that prototype is visible to your code.
An experiment with gcc 4.8.2 indicates that it is treated as a function with a visible prototype. (You can't store its address in a pointer as you could for an ordinary function, but that shouldn't be a problem). This program:
#include <stdio.h>
#include <string.h>
int main(void) {
unsigned int n = 21845; // 0x5555, popcount = 8
float x = 21845.0;
unsigned int x_rep;
memcpy(&x_rep, &x, sizeof x_rep);
if (sizeof x != sizeof x_rep) {
puts("WARNING: Sizes do not match");
}
printf("popcount(%u) = %d\n", n, __builtin_popcount(n));
printf("popcount(%g) = %d\n", x, __builtin_popcount(x));
printf("popcount(%u) = %d\n", x_rep, __builtin_popcount(x_rep));
return 0;
}
produces this output on my system:
popcount(21845) = 8
popcount(21845) = 8
popcount(1185589760) = 11
Which means that the value of x
is converted to unsigned int
, not just reinterpreted. When we explicitly reinterpret its representation, we get different results.
So unless gcc changes its implementation of builtin functions for some reason (that seems unlikely), passing a signed int
to __builtin_popcount
should work as expected, converting the int
value to unsigned int
. And assuming a 2's-complement representation for signed integers (which is a reasonably safe assumption), converting from int
to unsigned int
does not change the representation, so __builtin_popcount
will give you a correct count of the bits that are set in the representation of the int
, including the sign bit.
Of course if you don't want to depend on this, you can always explicitly convert the value to unsigned int
using a cast. Casts are often error-prone, and it's usually better to use implicit conversions, but in this case it might be a reasonable approach.
Having said all this, if you're computing the population count of a value, it almost certainly makes more sense to start with an unsigned value. It's likely that the signed int
value you're passing to __builtin_popcount
should have been defined as an unsigned int
in the first place.
Finally, you wrote that __builtin_popcount
is "defined for unsigned integers". Actually, it's defined only for type unsigned int
, not for unsigned integers in general. There are three different builtin functions:
int __builtin_popcount (unsigned int x);
int __builtin_popcountl (unsigned long x);
int __builtin_popcountll (unsigned long long x);
You need to use the right one for the type of data you're working with. Using __builtin_popcount
on an unsigned long long
object will likely ignore the upper half of the value, probably without a warning from the compiler.

- 254,901
- 44
- 429
- 631
-
But why does this function not perform implicit type conversion? https://en.cppreference.com/w/cpp/numeric/popcount – Kargath Oct 30 '21 at 03:50
-
@fqq That was added to the language in C++20. The standard specifically says that it's constrained to operate only on unsigned integer types. – Keith Thompson Oct 30 '21 at 04:16
To complement the other answers, here is a do-it-yourself, what-is-gcc-doing example. Let us write a simple testcase:
int f(int i){
return __builtin_popcount(i);
}
and compile it with gcc -c test.c -fdump-tree-all
. This creates several files, starting with test.c.003t.original
:
;; Function f (null)
;; enabled by -tree-original
{
return __builtin_popcount ((unsigned int) i);
}
So you can see that when __builtin_popcount
is called on a signed integer, gcc casts it to the documented argument type of unsigned int
.

- 7,550
- 2
- 30
- 53
Yes. You can pass signed int
as well — assuming negative number is represented as 2's complement (which is most on the modern systems).
If the number is positive, then it is as good as unsigned int
. If you pass a negative number however, say -1
, it will convert into a very large number of type unsigned int
, but it will not change the bits-pattern — hence the number of bits. signed
or unsigned
has nothing to do with bit-patterns, it has to do the interpretation of bits-pattern when computing value.
signed int i = -1; //i has N number of 1 bit
unsigned int j = -1; //j has N number of 1 bit as well.
//j becomes a very large number!
Hope that helps.
-
I don´t think that´s generally valid, eg. Section 4.7 ([conv.integral]). And it´s not even guaranteed that __builtin_something behaves like a normal function with unsigned parameter. – deviantfan Apr 26 '15 at 15:13
-
1@deviantfan: I added *"assuming negative number is represented as 2's compliment (which is most on the systems)."* – Nawaz Apr 26 '15 at 15:14
-
This assumes that the compiler provides the equivalent of a visible prototype for `__builtin_popcount`. It turns out that it does, but that's not 100% clear from the documentation. – Keith Thompson Apr 26 '15 at 18:39