barfatchen
barfatchen

Reputation: 1698

Strange behavior of int64_t and int in atomic fetch_add in c++11

I have the following c++ source test in MinGW in winx , g++ version is 4.8.1 : compiled : g++ -std=c++11 int64test.cpp -o int64test.exe

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <string.h>
#include <errno.h>
#include <atomic>
#include <iostream> 
#include <cstdint>

using namespace std ;

int main(int argc, const char *argv[])
{
    atomic<unsigned int> uintlocal1(1000) ;
    unsigned int uint1,uint2,uint3 ;
    uint1 = uintlocal1.fetch_add(1) ;
    uint2 = uintlocal1.fetch_add(1) ;
    uint3 = uintlocal1.fetch_add(1) ;   
    printf("(%d)(%d)(%d)(%d)\n",uint1,uint2,uint3,unsigned(uintlocal1)) ;

    atomic<uint64_t> uint64local1(1000) ;
    uint64_t u1,u2,u3 ;
    u1 = uint64local1.fetch_add(1) ;
    u2 = uint64local1.fetch_add(1) ;
    u3 = uint64local1.fetch_add(1) ;    
    printf("(%d)(%d)(%d)(%d)\n",u1,u2,u3,unsigned(uint64local1)) ;
}

The answer is :

(1000)(1001)(1002)(1003)
(1000)(0)(1001)(0)

Obviously , the atomic uint64_t is wrong , while atomic int is right !! But I don't know what cause this problem , what should I modify so that I can use atomic correctly ...thanks !!

Upvotes: 0

Views: 1190

Answers (1)

bames53
bames53

Reputation: 88225

You are using incorrect formats for the line printing uint64_t data. When I compile your code my compiler produces the following warnings:

main.cpp:18:33: warning: format specifies type 'int' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Wformat]
    printf("(%d)(%d)(%d)(%d)\n",u1,u2,u3,unsigned(uint64local1)) ;
             ~~                 ^~
             %llu
main.cpp:18:36: warning: format specifies type 'int' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Wformat]
    printf("(%d)(%d)(%d)(%d)\n",u1,u2,u3,unsigned(uint64local1)) ;
                 ~~                ^~
                 %llu
main.cpp:18:39: warning: format specifies type 'int' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Wformat]
    printf("(%d)(%d)(%d)(%d)\n",u1,u2,u3,unsigned(uint64local1)) ;
                     ~~               ^~
                     %llu

Note: You can enable a similar format checking warning in GCC 4.8.1 using the flag -Wformat, or better yet, -Wall.


On my platform the types int and unsigned long long are not layout compatible and so when printf tries to access a vararg specified by %d when the actual passed argument is uint64_t the result will be undefined behavior.

The normal formatters for printf such as %d and %llu are used for built in types such as int or unsigned long long. The types in stdint.h are not built in and may correspond to different built-in types on different platforms, requiring different formatters on each platform.

For example int64_t might be the same as int on one platform and the same as long long on a different platform. Since to use int in printf you use the format specifier %d and to use long long you use the format specifier %lld, you cannot write portable code using stdint types and the normal format specifiers.


Instead, the header inttypes.h is provided with macros that contain the correct format specifiers. The macro for uint64_t is PRIu64. This macro will be defined to be whatever the correct format specifier is on your platform. Use it like this:

printf("(%" PRIu64 ")(%" PRIu64 ")(%" PRIu64 ")(%u)\n",u1,u2,u3,unsigned(uint64local1));

Make sure you get the spaces put in between the macro and the quoted string fragments, otherwise in C++11 the macros will not work correctly.

Here's a useful reference on the normal formatters: http://en.cppreference.com/w/cpp/io/c/fprintf

Here's a reference for the stdint.h types and formatters: http://en.cppreference.com/w/cpp/types/integer


Note: Using incorrect format specifiers with printf an easy mistake to make and leads to undefined behavior. One of the advantages of the iostream library is that this sort of mistake is impossible.

Upvotes: 3

Related Questions