【问题标题】:How to combine two 32-bit integers into one 64-bit integer?如何将两个 32 位整数组合成一个 64 位整数?
【发布时间】:2010-05-04 20:49:28
【问题描述】:

我有一个计数寄存器,它由两个 32 位无符号整数组成,一个用于值的高 32 位(最高有效字),另一个用于值的低 32 位(最低有效字) )。

C 中将这两个 32 位无符号整数组合并显示为大数的最佳方法是什么?

具体来说:

leastSignificantWord = 4294967295; //2^32-1

printf("Counter: %u%u", mostSignificantWord,leastSignificantWord);

这会打印得很好。

当数字增加到 4294967296 时,我拥有它,因此 leastSignificantWord 擦除为 0,而 mostSignificantWord(最初为 0)现在为 1。整个计数器现在应该读取 4294967296,但现在它只读取 10,因为我' m 只是将 mostSignificantWord 中的 1 和 leastSignificantWord 中的 0 连接起来。

我应该如何让它显示 4294967296 而不是 10?

【问题讨论】:

  • 只有当您使用 %8.8X 作为格式说明符来连接单词的十六进制值时,您的方法才有意义。要让它以十进制工作,最大整数值必须是 (10^n)-1,其中 n 是十进制位数,而在二进制机器上永远不会出现这种情况!

标签: c printf


【解决方案1】:

在这种情况下,使用具有 显式 大小的 无符号 整数可能是有利的:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
  uint32_t leastSignificantWord = 0;
  uint32_t mostSignificantWord = 1;
  uint64_t i = (uint64_t) mostSignificantWord << 32 | leastSignificantWord;
  printf("%" PRIu64 "\n", i);

  return 0;
}
输出

4294967296

分解(uint64_t) mostSignificantWord &lt;&lt; 32 | leastSignificantWord

  • (typename) 在 C 中执行 typecasting。它将值数据类型更改为 typename

    (uint64_t) 0x00000001 -> 0x0000000000000001

  • &lt;&lt; 执行left shift。在 C 中,无符号整数左移执行logical shift

    0x00000000000000001 0x0000000100000000

  • | 执行'bitwise or'(操作数位的逻辑或)。

    0b0101 | 0b1001 -> 0b1101

【讨论】:

  • 太棒了。不仅知道如何使用具有显式大小的 C99 类型,而且还了解如何使用相关的 printf 宏的人。如果可以的话+10。 SO-ers,联合起来!这个答案值得一提!
  • 有人能解释一下这条线是什么意思吗? uint64_t i = (uint64_t) mostSignificantWord
  • @Bei337: inttypes.h 是 C99 标准的一部分。它定义了printf/scanf 的格式说明符,例如PRIu64,它包括定义typedef 的stdint.h,例如uint32_t。如果您使用的是 MSVC++/Windows,请从 code.google.com/p/msinttypes 获取 inttypes.h
  • @Bei337:如果您将代码编译为 C 代码(而不是 C++)可能会有所帮助。 C++ 需要定义 __STDC_FORMAT_MACROS 以使用 PRI.. 宏。
  • @PremalShah: uint32_t lo = (uint32_t)n, hi = (n &gt;&gt; 32);,其中nunit64_t 变量。
【解决方案2】:
long long val = (long long) mostSignificantWord << 32 | leastSignificantWord;
printf( "%lli", val );

【讨论】:

  • 如果 mostSignificantWord 是 32 位整数,&lt;&lt; 32 将无法正常工作。它需要先转换为 64 位类型。顺便说一句,在 g++ 中,longlong long 都是 8 个字节。
  • @Alex: 在 x64 上的 g++ 中,long 是 64 位。在 x86 上它是 32 位。
  • 请使用 C99 类型 uint32_tuint64_t。这就是他们在那里的原因。
  • 输入是unsigned,所以在没有OP的情况下,输出不应该也是如此吗? long long 本身意味着 signed long long int
【解决方案3】:

我的看法:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;

data64 = (unsigned long long) high << 32 | low;

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

另一种方法:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;
unsigned char * ptr = (unsigned char *) &data;

memcpy (ptr+0, &low, 4);
memcpy (ptr+4, &high, 4);

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

两个版本都可以工作,并且它们的性能相似(编译器会优化 memcpy)。

第二个版本不适用于大端目标,但如果常量 3​​2 应该是 32 或 32ull,它就消除了猜测。当我看到常数大于 31 的变化时,我永远不确定。

【讨论】:

  • 移位量常量是32 还是32ULL 无关紧要,因为它们都具有相同的值,而这就是用于移位的全部。只要被移动的值的类型为unsigned long long,一切都是笨拙的。
  • 为什么还要展示非便携式解决方案?现在有人会复制它,然后代码会被转移到另一个应用程序中,最终会出现问题,并且需要永远调试。
  • 使用具有明确大小的 C99 类型。这就是他们在那里的原因。
【解决方案4】:

还有另一种使用数组和指针的方法:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
    // Two uint32_t to one uint64_t
    uint32_t val1[2] = {1000, 90000};
    uint64_t *val1_u64_ptr = (uint64_t*)val1; //intermediate pointer cast to avoid Wstrict-aliasing warnings
    uint64_t val2 = *val1_u64_ptr;
    printf("val2: %" PRIu64 "\n", val2);
    // val2: 386547056641000


    // back to uint32_t array from uint64_t
    uint64_t val3 = 386547056641000ull;
    uint32_t *val4 = (uint32_t*)&val3;
    printf("val4: %" PRIu32 ", %" PRIu32 "\n", val4[0], val4[1]);
    // val4: 1000, 90000
    
    return 0;
}

对我来说,这段代码更容易理解和阅读。您只是在内存中创建一个具有两个32-bit unsigned int 的连续空间,然后将相同的内存空间读取为单个64-bit unsigned int 值,反之亦然。不涉及仅将内存作为不同类型读取的操作。

编辑

忘了提到,如果您已经从某个地方读取了 64-bit array,那么您可以轻松地将所有内容读取为 32-bit array 对:

#include <stdio.h>
#include <inttypes.h>

int main() {
    uint64_t array64[] = {
        386547056641000ull,
        93929935171414ull,
        186655006591110ull,
        73141496240875ull,
        161460097995400ull,
        351282298325439ull,
        97310615654411ull,
        104561732955680ull,
        383587691986172ull,
        386547056641000ull
    };

    int n_items = sizeof(array64) / sizeof(array64[0]);
    
    uint32_t* array32 = (uint32_t*)&array64;
    for (int ii = 0; ii < n_items * 2; ii += 2) {
        printf("[%" PRIu32 ", %" PRIu32 "]\n", array32[ii], array32[ii + 1]);
    }

    return 0;
}

输出:

[1000, 90000]
[3295375190, 21869]
[22874246, 43459]
[2498157291, 17029]
[3687404168, 37592]
[1218152895, 81789]
[3836596235, 22656]
[754134560, 24345]
[4162780412, 89310]
[1000, 90000]

使用联合结构

如果使用 https://stackoverflow.com/a/2810339/2548351 中的结构联合,则更好、更易读:

#include <stdio.h>
#include <inttypes.h>

typedef union {
  int64_t big;
  struct {
    int32_t x;
    int32_t y;
  };
} xy_t;

int main() {
    // initialize from 64-bit
    xy_t value = {386547056641000ull};
    printf("[%" PRIu32 ",%" PRIu32 "]\n", value.x, value.y);
    // [1000, 90000]
    
    // initialize as two 32-bit
    xy_t value2 = {.x = 1000, .y = 90000};
    printf("%" PRIu64, value.big);
    // 386547056641000

    return 0;
}

【讨论】:

  • 如果包含 -Wall 或 -Werror -> 结果(错误:取消引用类型双关指针将破坏严格别名规则 [-Werror=strict-aliasing])。您的 gcc 8.3 代码: uint64_t val2 = ((uint64_t)val1);只有没有 -Wall 和 -Werror :(
  • 是的,严格别名会引发警告,这是你必须告诉编译器你知道你在将指针转换为不同类型时正在做什么的地方。大胆地用以下代码装饰这段代码:代码前#pragma GCC diagnostic push#pragma GCC diagnostic ignored "-Wstrict-aliasing",代码后#pragma GCC diagnostic pop
  • 另一种方法是分两步取消引用,而不是uint64_t val2 = *(uint64_t*)val1,您将转换指针类型uint64_t *val2 = (uint64_t*)val1,然后取消引用该指针uint64_t result = *val2,我在这里尝试过,一切正常。
  • 不要永远这样做。当您根据此答案编写代码时,您只是希望您的编译器不会对您执行此操作:C undefined behavior. Strict aliasing rule, or incorrect alignment?。我个人更喜欢不依赖“天啊,我希望这行得通!!”的代码就像这个答案中发布的代码一样。关于这个答案中发布的代码,任何人都可以说的最好的说法是,“我还没有观察到它失败。但是。”写出比这更好的代码。
  • 好吧,我并不完全同意这一点。如果你曾经对任何人这么说,那就等于说除了专家之外的任何人都可以编写任何代码,你会限制任何人实际学习任何东西,甚至利用他们的创造力跳出框框思考并创造新的解决方案。我只是想到,与其将一个uint32_t 向左移位并将另一个连接(或)到右侧,不如将两个连续的uint32_t 放入内存中并将它们读取为uint64_t。我不应该测试一下吗?我根本不是专家,但我也不喜欢在盒子里思考。
【解决方案5】:

我经常用十六进制打印,而不是尝试打印十进制。

因此...

printf ("0x%x%08x\n", upper32, lower32);

或者,根据架构、平台和编译器,有时您可以摆脱类似...

printf ("%lld\n", lower32, upper32);

printf ("%lld\n", upper32, lower32);

但是,这种替代方法非常依赖机器(字节序,以及 64 与 32 位,...),一般不推荐。

希望这会有所帮助。

【讨论】:

    【解决方案6】:

    当upper32 和lower32 均为负数时,此代码有效:

    data64 = ((LONGLONG)upper32<< 32) | ((LONGLONG)lower32& 0xffffffff);
    

    【讨论】:

      【解决方案7】:

      您可以通过将 32 位值写入内存中的正确位置来实现:

      unsigned long int data64;
      data64=lowerword
      *(&((unsigned int)data64)+1)=upperword;
      

      这取决于机器,例如它不能在大端处理器上正常工作。

      【讨论】:

      • 你能详细解释一下你的答案吗
      • 这只适用于 little-endian 机器。还有一个假设是 unsigned long 是 64 位,unsigned int 是 32 位(可以使用 uint32 和 uint64)。现在解释 data64=lowerword 这会将 lowerword 复制到 data64 的低字节。 (unsigned int)data64 将其转换为 32 位。 &((unsigned int)data64) 给我 unsigned int 类型的地址加 1 将地址增加到 data64 的高字节最后 * 这将高位字存储在那些高字节中。
      • 这有未定义的行为;它违反了严格的别名。如果您想这样做,请使用memcpy,或者最好不要这样做。编译懂((uint64_t)a&lt;&lt;32) | b
      【解决方案8】:

      在游戏后期,但我需要类似的东西来表示 32 位嵌入式环境上的 64 位整数的数字 base10 字符串..

      因此,受http://mathforum.org/library/drmath/view/55970.html 的启发,我编写了这段代码,可以完成问题中提出的操作,但不限于 base10:可以转换为从 2 到 10 的任何基数,并且可以轻松扩展到基数 N。

      void stringBaseAdd(char *buf, unsigned long add, int base){
      
          char tmp[65], *p, *q;
          int l=strlen(buf);
          int da1, da2, dar;
          int r;
      
          tmp[64]=0;
          q=&tmp[64];
          p=&buf[l-1];
          r=0;
          while(add && p>=buf){
      
              da1=add%base;
              add/=base;
              da2=*p-'0';
              dar=da1+da2+r;
      
              r=(dar>=base)? dar/base: 0;
              *p='0'+(dar%base);
              --p;
          }
      
          while(add){
      
              da1=add%base;
              add/=base;
              dar=da1+r;
      
              r=(dar>=base)? dar/base: 0;
              --q;
              *q='0'+(dar%base);
          }
      
          while(p>=buf && r){
      
              da2=*p-'0';
              dar=da2+r;
      
              r=(dar>=base)? 1: 0;
              *p='0'+(dar%base);
              --p;
          }
      
          if(r){
      
              --q;
              *q='0'+r;
          }
      
          l=strlen(q);
          if(l){
      
              memmove(&buf[l], buf, strlen(buf)+1);
              memcpy(buf, q, l);
          }
      }
      void stringBaseDouble(char *buf, int base){
      
          char *p;
          int l=strlen(buf);
          int da1, dar;
          int r;
      
          p=&buf[l-1];
          r=0;
          while(p>=buf){
      
              da1=*p-'0';
              dar=(da1<<1)+r;
      
              r=(dar>=base)? 1: 0;
              *p='0'+(dar%base);
              --p;
          }
      
          if(r){
      
              memmove(&buf[1], buf, strlen(buf)+1);
              *buf='1';
          }
      }
      void stringBaseInc(char *buf, int base){
      
          char *p;
          int l=strlen(buf);
          int da1, dar;
          int r;
      
          p=&buf[l-1];
          r=1;
          while(p>=buf && r){
      
              da1=*p-'0';
              dar=da1+r;
      
              r=(dar>=base)? 1: 0;
              *p='0'+(dar%base);
              --p;
          }
      
          if(r){
      
              memmove(&buf[1], buf, strlen(buf)+1);
              *buf='1';
          }
      }
      void stringLongLongInt(char *buf, unsigned long h, unsigned long l, int base){
      
          unsigned long init=l;
          int s=0, comb=0;
      
          if(h){
      
              comb=1;
              init=h;
              while(!(init&0x80000000L)){
      
                  init<<=1;
                  init|=(l&0x80000000L)? 1: 0;
                  l<<=1;
                  s++;
              }
          }
      
          buf[0]='0';
          buf[1]=0;
          stringBaseAdd(buf, init, base);
      
          if(comb){
      
              l>>=s;
              h=0x80000000L>>s;
              s=sizeof(l)*8-s;
              while(s--){
      
                  stringBaseDouble(buf, base);
                  if(l&h)
                      stringBaseInc(buf, base);
      
                  h>>=1;
              }
          }
      }
      

      如果你要求

      char buff[20];
      stringLongLongInt(buff, 1, 0, 10);
      

      你的 buff 将包含 4294967296

      【讨论】:

        猜你喜欢
        • 2011-09-12
        • 1970-01-01
        • 2019-08-17
        • 1970-01-01
        • 2012-03-16
        • 1970-01-01
        • 2013-12-12
        • 2013-12-22
        • 1970-01-01
        相关资源
        最近更新 更多