1

Тема: C uin32_t uint64_t

Сьогодні навалився на наступний баг

#include <stdio.h>
#include <stdint.h>
#include <limits.h>
 
void f(uint32_t a) // мало б бути uint64_t
{
    printf("%x\n", a);
    printf("__WORDSIZE = %u\n", __WORDSIZE);
}

int main(void) {
    uint64_t a = -1;
    f(a); // ?
    return 0;
}

online

Тобто uint64_t перетворюється в uint32_t і ніякого loses precision при -Wall. Я б ще зрозумів це при  __WORDSIZE = 32, але при 64 мене дивує чого компілятор мовчить.

Хтось може пояснити чого так?

Подякували: Arete, leofun012

2

Re: C uin32_t uint64_t

А змінні хіба не мовчки приводяться?
З константою дає попередження

    f(-1ULL);

In function 'int main()':
15:8: warning: large integer implicitly truncated to unsigned type [-Woverflow]

p.s. від __WORDISZE це не повинно залежати.
<stdint.h>-типи фіксовані по поведінці і навіть на архітектурі «без байтів» (мінімальна одиниця адресації 16 біт) uint8_t має себе поводити як на 8-бітовому мікроконтролері :-)
Тут gcc на 64-бітній машині що так, що з ключиком -m32 поводиться однаково — на константу бурчить, змінну приводить мовчки.

Подякували: Arete, voland, 0x9111A, leofun014

3

Re: C uin32_t uint64_t

Ооок... а яка різниця компілятору змінна чи константа якщо в функцію f параметр передається 'by value'.

Подякували: 0x9111A, leofun012

4

Re: C uin32_t uint64_t

by value — це все одно «створити змінну, проініціалізувати, віддати у функцію» (push чогось у стек при передаванні аргументів через стек — це теж «створити змінну»).
Якось так.

int main(void) {
    uint64_t a = -1;
    uint32_t b = -1ULL;  // Попередження, обрізання константи
    uint32_t c = a;      // Мовчки
    return c;
}
Подякували: leofun011

5

Re: C uin32_t uint64_t

UPD: Взагалі всі ці попередження — добра воля компілятора, у стандарті де-юре про них не написано наче, а стандарту де-факто наче і нема.
У мене для дрібних контролерів часто є присвоювання до меншого розміру (добре, це зробили у 32 бітах, далі досить 16, а коду менше і робота швидша) — ніколи попереджень не видає, на невдалі константи — обов'язково. При тому, що я pedantic прибираю з ключів лише через сторонні бібліотеки, які ліньки чистити.

Подякували: leofun011

6 Востаннє редагувалося 0x9111A (05.09.2016 13:27:07)

Re: C uin32_t uint64_t

ReAl написав:

А змінні хіба не мовчки приводяться?
З константою дає попередження

    f(-1ULL);

In function 'int main()':
15:8: warning: large integer implicitly truncated to unsigned type [-Woverflow]

Добре що хоч так, але то мене не врятувало

ReAl написав:

by value — це все одно «створити змінну, проініціалізувати, віддати у функцію» (push чогось у стек при передаванні аргументів через стек — це теж «створити змінну»).

Ну от вам ассемблі

0x400520 <main+8>               movq   $0xffffffffffffffff,-0x8(%rbp)                                                                                                             
0x400528 <main+16>              mov    -0x8(%rbp),%rax  ; -1 в Rax                                                                                                                              
0x40052c <main+20>              mov    %eax,%edi        ; беремо з Eax
0x40052e <main+22>              callq  0x4004f6 <f>  

А з О3 то взагалі

0x400404 <main+4>       mov    $0xffffffff,%esi
0x400409 <main+9>       mov    $0x4005b4,%edi                                                                                                                                          
0x40040e <main+14>      xor    %eax,%eax                                                                                                                                                
0x400410 <main+16>      callq  0x4003f0 <printf@plt>  

Якось воно не гарно

Подякували: leofun011

7

Re: C uin32_t uint64_t

Асемблерне — то вже після всіх оптимізаторів, там може бути що завгодно, що «не міняє зовні видиму поведінку програми», причому у сферичному всесвіті без часу (бо цикли тупої затримки без volatile так само викидаються оптимзітором). А попередження про розміри десь на перших проходах компілятора.

Якщо б воно попереджало про зміну розміру при uint32_var = uint64_var; а також як при int_var = float_var; так і при float_var = int32_var; (у першому випадкові може не влізти через експоненту, у другому — через те, що мантиса у float32 менша за 32 розряди і може бути втрата точності).

При цьому вийде, що треба скрізь explicit приведення float_var = (float)int32_var; і так далі. По дорозі всю ієрархію автоматичного приведення типів зі стандарту викидати як непотрібну.

Може, так було б і правильно з якихось суто теоретичних міркувань, але ж то буде вже не C (або C++ зовсім втратить сумісність як з С, так і з попередніми версіями себе).

Подякували: leofun01, 0x9111A2

8

Re: C uin32_t uint64_t

Я з вами згоден, ассемблерне кинув як "доказ" того що це обрізання має цільком "фізичну" реалізацію, і тому її певно було б гарно заборонити.