【问题标题】:sprintf raw bytes to string in C?sprintf原始字节到C中的字符串?
【发布时间】:2020-01-24 20:27:29
【问题描述】:

我通过 C 中的线路发送一些原始字节(使用 HTTP)。我目前正在这样做:

// response is a large buffer
int n = 0; // response length
int x = 42; // want client to read x
int y = 43; // and y 

// write a simple HTTP response containing a 200 status code then x and y in binary format
strcpy(response, "HTTP/1.1 200\r\n\r\n");
n += 16; // status line we just wrote is 16 bytes long
memcpy(response + n, &x, sizeof(x));
n += sizeof(x);
memcpy(response + n, &y, sizeof(y));
n += sizeof(y);
write(client, response, n);

在 JavaScript 中,然后我使用如下代码读取这些数据:

request = new XMLHttpRequest();
request.responseType = "arraybuffer";
request.open("GET", "/test");
request.onreadystatechange = function() { if (this.readyState === XMLHttpRequest.DONE) { console.log(new Int32Array(this.response)) } }
request.send();

它应该打印[42, 43]

我想知道是否有更优雅的方法可以在服务器端执行此操作,例如

n += sprintf(response, "HTTP/1.1 200\r\n\r\n%4b%4b", &x, &y);

%4b 是一个虚构的格式说明符,它只是说:将该地址中的 4 个字节复制到字符串中(即“*\0\0\0”)是否有像虚构的格式说明符%4b 做这样的事情?

【问题讨论】:

  • sprintf 用于字符串。即 - char 数组以 \0 结尾。所以不,它不适合任意二进制数据。
  • 我不这么认为。 sprintf() 通常用于创建可打印的字符串,而不是二进制。
  • 那些memcpy 调用将使您的代码依赖于字节序。
  • 如果要发送二进制数据,应该使用定义明确的序列化格式。
  • 附加 "*\0\0\0" 将终止第一个 '\0' 处的字符串 - sprintf() 并不真正适合此目的。

标签: c string format-specifiers


【解决方案1】:

这是一个XY问题,你问的是如何使用sprintf()来解决你的问题,而不是简单地问如何解决你的问题。你的实际问题是如何让代码更“优雅”。

在单个写入操作中发送数据没有特别的原因 - 网络堆栈缓冲将确保数据被有效打包:

static const char header[] = "HTTP/1.1 200\r\n\r\n" ;
write( client, header, sizeof(header) - 1 ) ;
write( client, &x, sizeof(x) ) ;
write( client, &y, sizeof(y) ) ; 

请注意,X 和 Y 将按照本机机器字节顺序写入,这在接收方可能不正确。更笼统地说:

static const char header[] = "HTTP/1.1 200\r\n\r\n" ;
write( client, header, sizeof(header) - 1 ) ;

uint32_t nl = htonl( x ) ;
write( client, &nl, sizeof(nl) ) ;

nl = htonl( y ) ;
write( client, &nl, sizeof(nl) ) ; 

【讨论】:

    【解决方案2】:

    有没有像虚构的 %4b 这样的格式说明符?

    不,没有,你的方法很好。我建议使用snprintf 并进行一些检查以避免缓冲区溢出,添加 ex. static_assert(sizeof(int) == 4, "") 检查平台是否使用大端和类似的环境和错误处理并避免未定义的行为检查。

    也就是说,您可以多次使用 %c printf 说明符,例如 "%c%c%c%c", ((char*)&x)[3], ((char*)&x)[2], ((char*)&x)[1], ((char*)&x)[0] 打印 4 个字节。您可以将其包装在宏中并执行以下操作:

    #include <stdio.h>
    
    #define PRI_BYTES_4  "%c%c%c%c"
    #define ARG_BYTES_BE_4(var) \
        ((const char*)&(var))[3], \
        ((const char*)&(var))[2], \
        ((const char*)&(var))[1], \
        ((const char*)&(var))[0]
    
    int main() {
        int var = 
            'l' << 24 |
            'a' << 16 | 
            'm' << 8 |
            'e';
        printf("Hello, I am " PRI_BYTES_4 ".\n",
            ARG_BYTES_BE_4(var));
        // will print `Hello, I am lame.`
    }
    

    【讨论】:

    • int var = 'l' &lt;&lt; 24 | ... 有 UB 的风险,因为 l 实际上是一个 int 值,所以左移它有溢出和 UB 的风险。 uint32_t var = ( ( uint8_t ) 'l' ) &lt;&lt; 24 | ... 会更好。
    • @AndrewHenle ( ( uint8_t ) 'l' ) &lt;&lt; 24 具有与'l' &lt;&lt; 24 相同的溢出风险。你想要( ( uint32_t ) 'l' ) &lt;&lt; 24吗?
    • @chux-ReinstateMonica 是的。嗬!我会恳求非常漫长的一天......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    • 2013-03-20
    • 2018-06-30
    • 1970-01-01
    • 2018-09-26
    • 2018-11-13
    相关资源
    最近更新 更多