简体   繁体   English

C99 复合文字传递给函数参数并由同一函数返回

[英]C99 compound literal passed to function parameter and returned by the same function

I want to convert a uuid to hex string in C99 and pass it to a log function which uses printf format under the hood.我想在 C99 中将 uuid 转换为十六进制字符串,并将其传递给在后台使用 printf 格式的日志函数。 I want to avoid the separate allocation of local variable because if the log is disabled then the preprocessor removes the function call and the variable becomes unused so a warning is emitted.我想避免局部变量的单独分配,因为如果日志被禁用,那么预处理器会删除函数调用并且变量变得未使用,因此会发出警告。

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

typedef struct {
  uint8_t data[8];
} uuid_64_t;

#define UID_64_STR_MAX_SIZE 24

#define UUID_64_TO_STRING(uuid_64, separator, uppercase)      \
  uuid_64_to_string((char[UID_64_STR_MAX_SIZE]){ 0 },         \
                    sizeof((char[UID_64_STR_MAX_SIZE]){ 0 }), \
                    uuid_64,                                  \
                    separator,                                \
                    uppercase)

const char *bytes_to_hex(char *buffer,
                         uint32_t buffer_size,
                         const uint8_t *bytes,
                         uint32_t bytes_size,
                         char separator,
                         bool uppercase)
{
  const char hex_char_uppercase[] = { '0', '1', '2', '3', '4', '5', '6', '7',
                                      '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
  const char hex_char_lowercase[] = { '0', '1', '2', '3', '4', '5', '6', '7',
                                      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

  const char *hex_char = uppercase ? hex_char_uppercase : hex_char_lowercase;
  uint32_t total_size, total_separator_size;

  // If the separator is set the null character then no separator is used so
  // the multiplication by zero results in zero total separator size.
  // There is a separator after each two hex characters except for the last two.
  total_separator_size = (bytes_size - 1) * (separator != '\0');

  // One character shall be reserved for the terminating null character
  total_size = 2 * bytes_size + total_separator_size + 1;

  if ((buffer == NULL) || (bytes == NULL) || (buffer_size < total_size)) {
    return "INVALID";
  }

  uint32_t out_idx = 0;
  for (uint32_t in_idx = 0; in_idx < bytes_size; in_idx++) {
    buffer[out_idx++] = hex_char[(bytes[in_idx] >> 4) & 0xF];
    buffer[out_idx++] = hex_char[(bytes[in_idx] >> 0) & 0xF];
    if (separator != '\0' && (in_idx + 1) < bytes_size) {
      buffer[out_idx++] = separator;
    }
  }

  buffer[out_idx] = '\0';
  return buffer;
}

const char *uuid_64_to_string(char *buffer,
                              uint32_t buffer_size,
                              const uuid_64_t *uuid_64,
                              char separator,
                              bool uppercase)
{
  return bytes_to_hex(buffer,
                      buffer_size,
                      uuid_64->data,
                      sizeof(uuid_64->data),
                      separator,
                      uppercase);
}
 
int main(void)
{
    printf("uuid=%s\r\n", UUID_64_TO_STRING(&uuid_64, ':', true));
}

The idea is to call a function through a macro which passes a compound literal as the buffer parameter.这个想法是通过一个宏调用一个函数,该宏将复合文字作为缓冲区参数传递。 The compound literal allocates a local variable where the uuid_64_to_string function writes the hex characters through bytes_to_hex function.复合文字分配一个局部变量,uuid_64_to_string 函数通过 bytes_to_hex 函数写入十六进制字符。 The uuid_64_to_string returns this passed compound literal in order to use it directly in a printf-like log call. uuid_64_to_string 返回这个传递的复合文字,以便在类似 printf 的日志调用中直接使用它。 The only problem can be the lifetime of the compound literal and I am a little be unsure about this.唯一的问题可能是复合文字的生命周期,我对此有点不确定。 According to C99 standard:根据C99标准:

The value of the compound literal is that of an unnamed object initialized by the initializer list.复合文字的值是由初始化列表初始化的未命名对象的值。 If the compound literal occurs outside the body of a function, the object has static storage duration;如果复合文字出现在函数体之外,则该对象具有静态存储持续时间; otherwise, it has automatic storage duration associated with the enclosing block否则,它具有与封闭块关联的自动存储持续时间

So as I interpret the standard this should be well-defined behavior because the printf call and uuid_64_to_string call are in the same block.因此,当我解释标准时,这应该是明确定义的行为,因为 printf 调用和 uuid_64_to_string 调用在同一个块中。 What is you opinion?你有什么意见?

The macro UUID_64_TO_STRING expands to a function call to uuid_64_to_string passing a pointer to a compound literal (char[UID_64_STR_MAX_SIZE]){ 0 } whose scope is the enclosing block in the main() function.UUID_64_TO_STRING扩展为对uuid_64_to_string的函数调用,传递一个指向复合文字(char[UID_64_STR_MAX_SIZE]){ 0 }的指针,其范围是main()函数中的封闭块。

The function uuid_64_to_string returns its first argument, hence the pointer to the local array.函数uuid_64_to_string返回它的第一个参数,即指向本地数组的指针。 It is OK to pass that to printf as an argument because the object it points to is a C string and has a lifetime that covers the execution of the printf call.可以将它作为参数传递给printf ,因为它指向的对象是 C 字符串,并且具有涵盖printf调用执行的生命周期。

Conversely, it would be a mistake to return this pointer to the calling function or to store it into a pointer used outside the current scope:相反,将 this 指针返回给调用函数或将其存储到当前范围之外使用的指针中是错误的:

int main() {
    printf("uuid=%s\r\n", UUID_64_TO_STRING(&uuid_64, ':', true)); // OK
    return 0;
}

This use is invalid:此用法无效:

char *hexify(uuid_64_t *id) {
    return UUID_64_TO_STRING(id, ':', true); // NOT OK
}

int main() {
    printf("uuid=%s\r\n", hexify(&uuid_64)); // NOT OK
    return 0;
}

Note that the scoping issue may be subtle:请注意,范围问题可能很微妙:

int main() {
    const char *p = "invalid id";
    if (isValidID(uuid_64))
        p = UUID_64_TO_STRING(&uuid_64, ':', true);

    printf("uuid=%s\r\n", p); // OK
    return 0;
}
int main() {
    const char *p = "invalid id";
    if (isValidID(uuid_64)) {
        p = UUID_64_TO_STRING(&uuid_64, ':', true);
    }
    printf("uuid=%s\r\n", p); // NOT OK
    return 0;
}

While this macro seems useful, it should be used with care, probably only as a function argument.虽然这个宏看起来很有用,但应该小心使用它,可能只用作函数参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM