C99复合字面传递给函数参数并通过相同函数返回
我想将UUID转换为C99中的十六进制字符串,然后将其传递给使用引擎盖下方的printf格式的日志函数。我想避免局部变量的单独分配,因为如果禁用日志,则预处理器删除了函数调用,并且该变量未使用,因此发出警告。
#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));
}
这个想法是通过宏来调用一个函数,该函数将复合文字作为缓冲区参数传递。复合文字分配了一个局部变量,其中uuid_64_to_string函数通过bytes_to_hex函数写入十六进制字符。 UUID_64_TO_STRING返回此传递的复合文字,以直接在类似于printf的日志调用中使用它。 唯一的问题可能是化合物字面意思的寿命,我对此有些不确定。根据C99标准:
化合物字面的价值是未命名对象的价值 由初始化列表初始化。如果复合字面形式发生 在功能主体外,对象具有静态存储 期间;否则,它具有与 封闭块
因此当我解释标准时,这应该是明确定义的行为,因为printf调用和uuid_64_to_string调用在同一块中。 你认为什么?
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. 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. The uuid_64_to_string returns this passed compound literal in order to use it directly in a printf-like log call.
The only problem can be the lifetime of the compound literal and I am a little be unsure about this. According to C99 standard:
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.
What is you opinion?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
宏
uuid_64_to_string
将函数调用扩展到uuid_64_to_string
main()
函数中的封闭块。函数
uuid_64_to_string
返回其第一个参数,因此指向本地数组的指针。可以将其传递给printf
作为参数,因为它指向的对象是C字符串,并且具有涵盖printf
呼叫的执行的寿命。相反,将此指针返回到调用函数或将其存储到当前范围之外使用的指针中是错误的:
此用途无效:
请注意,范围问题可能很细微:
虽然此宏似乎有用,应该谨慎使用,可能仅作为函数参数。
The macro
UUID_64_TO_STRING
expands to a function call touuid_64_to_string
passing a pointer to a compound literal(char[UID_64_STR_MAX_SIZE]){ 0 }
whose scope is the enclosing block in themain()
function.The function
uuid_64_to_string
returns its first argument, hence the pointer to the local array. It is OK to pass that toprintf
as an argument because the object it points to is a C string and has a lifetime that covers the execution of theprintf
call.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 use is invalid:
Note that the scoping issue may be subtle:
While this macro seems useful, it should be used with care, probably only as a function argument.