【问题标题】:Scanning (sscanf) large integer into bytes将(sscanf)大整数扫描成字节
【发布时间】:2018-10-17 15:09:52
【问题描述】:

我需要 sscanf 一个 20 位数的号码,它刚好超出了 uint64_t 的范围。如果我不能在 uint64_t 中包含完整的数字,我希望将其设为 uint8_t[10](表示每个字节 2 位数字),我可以这样做:

const char *resp = "+QCCID: 89445003071864431280";

void parse_bytes() {
  uint8_t bytes[10] = {0};
  sscanf(resp, "+QCCID: %2hhu%2hhu%2hhu%2hhu%2hhu%2hhu%2hhu%2hhu%2hhu%2hhu", 
         &bytes[9], &bytes[8], &bytes[7], &bytes[6], &bytes[5], 
         &bytes[4], &bytes[3], &bytes[2], &bytes[1], &bytes[0]);

  // print just for testing, not the end result
  for(int i=9; i>=0; i--) {
    printf("%02d", bytes[i]);
  }
}

这工作得很好,但不是特别漂亮的代码。有没有更简洁的方法将这样的东西扫描到数组中?

为了澄清结果的意图:结果不会用于计算,它将通过蓝牙低功耗发送到另一台设备。

更新:我最终选择了@PetarVelev 建议的修改版本。由于此问题范围之外的原因,我使用了位字段结构。如果有人感兴趣,我将我的解决方案作为下面的答案之一发布。

【问题讨论】:

  • 你打算用这个号码做什么?你在做一些计算吗?如果有,具体是什么?
  • 它的目的是通过 BLE 到不同的设备,因此需要保持内存占用尽可能小。
  • 另一端在用它做什么?如果它只是作为 ID 打印,则将其作为文本发送更有意义。当然,它会占用 20 个字节,但使用更接近格式,因为它不那么麻烦和混乱。
  • 一旦到达另一端,它就会被保存在数据库中。我同意将其视为字符串会不那么麻烦。涉及到用户交互,所以我需要看看在 BLE 上多 10 个字节会导致多少额外延迟。

标签: c scanf


【解决方案1】:

代码可以使用循环和"%n" 来跟踪扫描进度。

可能不会更漂亮,但确实允许更轻松地更改 M。

#include <inttypes.h>
#include <stdio.h>
#define M 10

int parse_bytes(const char *resp) {
  uint8_t bytes[M] = {0};
  int n = 0;
  sscanf(resp, "+QCCID: %n", &n);
  if (n == 0) return -1;  // missing prefix 
  int m;
  for (m = 0; m < M; m++) { 
    resp += n;
    n = 0;
    sscanf(resp, "%2" SCNu8 "%n", &bytes[M - 1 - m], &n);
    if (n == 0) return -1;  // missing number
  } 
  if (resp[n]) return -1;  // trailing junk

  for (int i = m-1; i >= 0; i--) {
    printf("%02d", bytes[i]);
  }
  return m;
}

如果代码想要确保连续的数字并且没有空格或符号字符,可以添加测试:

    n = 0;
    if (!isdigit((unsigned char) *resp)) return -1;  // digit expected.
    sscanf(resp, "%2" SCNu8 "%n", &bytes[M - 1 - m], &n);

未处理的案件。 OP 断言“20 位数字”。如果少于那个,我们应该重新考虑整个方法。也许逐位扫描到d[M*2],然后从“右边”形成bytes[],或者:

  int n1, n2 = 0; 
  sscanf(resp, "+QCCID: %n%*[0-9]%n", &n1, &n2);
  if (n2 == 0 || n2 - n1 > 2*M) fail();

  // now process resp[n2-1] to resp[n1] with TBD code

许多可能性。

【讨论】:

    【解决方案2】:

    从问题和 OP 评论来看,您需要更小的占用空间,并且不会将该数字用于除打印之外的任何操作。

    那么,您为什么不使用相同的技术来获取uint64_t 中的前18 位数字和uint8_t 中的后两位数字。

    int main()
    {
        printf("Hello World");
        char arr[21] = "01234567890123456789";
        unsigned long long int a;
        unsigned char b;
    
        sscanf(arr, "%18llu%2hhu",&a,&b);
    
        printf("%18llu%2hhu",a,b);
    
        return 0;
     }
    

    注意前导零不会被打印出来。

    这会将内存占用减少 1 个字节,显然你不能少于这个,因为它不适合 8 个字节(uint64_t)。

    【讨论】:

    • 我确实考虑过这一点,但这也会导致代码笨拙。我现在需要移动 2 个变量而不是 1 个。澄清一下,它不用于打印(我只是将其作为最小示例)。最终结果将通过低功耗蓝牙发送到另一台设备。
    • 这只是存储信息。从理论上讲,它可以以 65 位存储,但这对您没有帮助,因为它是逐字节发送的。所以 9 是最小值。您可以将信息转换为您将发送的 9 个字节,然后在另一台设备上表示它们。考虑创建一个uint64_tuint8_t 的结构,假设您的函数需要一个指针,将指向该结构的指针转换为uint8_t* 并发送9 个字节。
    【解决方案3】:

    我的最终解决方案如下。我最终得到了一个位字段结构,因为事实证明我需要在其他地方使用前 6 位数字。此外,这是在带有不支持 64 位格式的newlib nano 的嵌入式系统上运行的,因此我无法扫描/打印uint64_t

    当然,由于不能获取位域的地址,所以我先扫描到uint32_t,然后创建结构体。

    typedef struct __attribute__((packed)) {
      uint32_t d1_6   : 20;
      uint32_t d7_12  : 20;
      uint32_t d13_20 : 32;
    } lte_sim_iccid_t;
    
    lte_sim_iccid_t parse_into_struct() {
      uint8_t bytes[10] = {0};
    
      uint32_t i1 = 0, i2 = 0, i3 = 0;
      sscanf(resp, "+QCCID: %6u%6u%8u", &i1, &i2, &i3);
    
      lte_sim_iccid_t s = { i1, i2, i3 };
    
      // sanity check
      printf("ICCID: %u %u %u (size %lu bytes)\n", s.d1_6, s.d7_12, s.d13_20, sizeof(s));
      return s;
    }
    

    【讨论】:

      猜你喜欢
      • 2023-04-05
      • 2012-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      • 2014-04-02
      相关资源
      最近更新 更多