【问题标题】:Padding with leading zero in string works in Solaris but not in RHEL用前导零填充字符串在 Solaris 中有效,但在 RHEL 中无效
【发布时间】:2017-10-24 05:16:45
【问题描述】:

此程序在一个平台上打印带有前导 0 的字符串,但在另一个平台上不打印。

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

int main(void)
{
   char test[20];
   char a[4]="a12";
   sprintf(test,"%019s\n",a);
   printf("%s\n",test);
   return 0;
}

输出:

Solaris : 0000000000000000a12
RHEL    :                 a12 (left padding with spaces)

如何在两个平台上获得前导零?

【问题讨论】:

  • 0 标志仅适用于数字转换。为什么不能执行 putchar 0、19 之类的操作然后打印字符串?
  • @SameerMahajan。这是我的要求。它在 solaris 中工作,但在 linux 中不工作。

标签: c linux string printf solaris


【解决方案1】:

您在两个系统上都调用了未定义的行为;你得到不同的结果并不奇怪。

printf() 的 POSIX 规范说:

0
对于diouxXaAef,@9876543343@,f,@98765 987654336@ 和 G 转换说明符,前导零(在符号或基数的任何指示之后)用于填充字段宽度而不是执行空格填充,除非在转换无穷大或 NaN 时。如果“0”和“-”标志都出现,则“0”标志将被忽略。对于diouxX 转换说明符,如果指定了精度,则应忽略“0”标志。 [CX] ⌦ 如果 '0' 和 &lt;apostrophe&gt; 标志都出现,则在零填充之前插入分组字符。对于其他转换,行为未定义。 ⌫

(关于解析该语句的注意事项。“⌫”的出现表明“其他转换”注释仅适用于同时出现的 0'。但是,检查 C11 标准(ISO/ IEC 9899:2011),它说:

0
对于diouxXaAeEFEFE 987654365@ 和 G 转换,前导零 (在符号或基础的任何指示之后)用于填充字段宽度而不是 比执行空间填充,除非在转换无穷大或 NaN 时。如果 0- 标志都出现,0 标志被忽略。对于diouxX 转换,如果指定了精度,0 标志将被忽略。对于其他 转换,行为未定义。

我认为结束标记“⌫”在 POSIX 材料中放错了位置。)

因此,要获得可靠的行为,您必须更有创造力。第一步是从格式字符串中删除0

您还需要增加缓冲区的大小;你写的超出了结尾。

可能是这样的:

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

int main(void)
{
    char test[25];
    char a[4] = "a12";
    snprintf(test, sizeof(test), "%19s\n", a);
    size_t leading_blanks = strspn(test, " ");
    memset(test, '0', leading_blanks);
    printf("%s", test);
    return 0;
}

输出:

0000000000000000a12

如果您需要在一行中处理多个值,每个值都需要补零,有多种方法可以处理它。一种选择是这样的(通过所有相同大小的字段来简化用例):

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

int main(void)
{
    char buffer[128];
    char *data[] = { "a12", "syzygy.sv.example.com", "192.168.234.119", "Quasimodo" };
    snprintf(buffer, sizeof(buffer), "%20.20s%20.20s%20.20s%20.20s",
             data[0], data[1], data[2], data[3]);
    for (int i = 0; i < 4; i++)
    {
        size_t leading_blanks = strspn(&buffer[20*i], " ");
        memset(&buffer[20*i], '0', leading_blanks);
    }
    printf("%s\n", buffer);
    return 0;
}

输出是:

00000000000000000a12syzygy.sv.example.co00000192.168.234.11900000000000Quasimodo

您可以小心地创建更精细的格式:

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

struct Field
{
    int width;
    char pad;
};

static struct Field fields[] =
{
    { .width = 12, .pad = '0', },
    { .width = 30, .pad = '@', },
    { .width = 15, .pad = '.', },
    { .width = 25, .pad = '-', },
};
enum { NUM_FIELDS = sizeof(fields) / sizeof(fields[0]) };

static char *data[][4] =
{
    { "a12",     "syzygy.sv.example.com", "192.168.234.119", "Quasimodo"             },
    { "zzx2341", "zulu.za.example.com",   "192.168.23.19",   "Beowulf"               },
    { "reynard", "coffee.br.example.com", "192.168.5.9",     "William the Conqueror" },
    { "peanut",  "koala.au.example.com",  "192.168.93.12",   "Quasimodo"             },
};
enum { NUM_ROWS = sizeof(data) / sizeof(data[0]) };

int main(void)
{
    for (int i = 0; i < NUM_ROWS; i++)
    {
        char   buffer[1024];
        char  *bufptr = buffer;
        size_t buflen = sizeof(buffer);
        for (int j = 0; j < NUM_FIELDS; j++)
        {
            int nbytes = snprintf(bufptr, buflen, "[%*.*s]", fields[j].width,
                                  fields[j].width, data[i][j]);
            if ((size_t)nbytes > buflen)
            {
                fprintf(stderr, "Overlength: %zu required\n",
                        sizeof(buffer) - buflen + nbytes + 1);
                return 1;
            }
            size_t leading_blanks = strspn(bufptr + 1, " ");
            memset(bufptr + 1, fields[j].pad, leading_blanks);
            bufptr += nbytes;
            buflen -= nbytes;
        }
        printf("%s\n", buffer);
    }
    return 0;
}

输出:

[000000000a12][@@@@@@@@@syzygy.sv.example.com][192.168.234.119][----------------Quasimodo]
[00000zzx2341][@@@@@@@@@@@zulu.za.example.com][..192.168.23.19][------------------Beowulf]
[00000reynard][@@@@@@@@@coffee.br.example.com][....192.168.5.9][----William the Conqueror]
[000000peanut][@@@@@@@@@@koala.au.example.com][..192.168.93.12][----------------Quasimodo]

【讨论】:

    【解决方案2】:

    你可以试试这样的:

    #include<stdio.h>
    #include<string.h>
    
    int main()
    {
        char a[4]="a12";
        int i;
        for (i = 0; i < (19 - strlen(a)); i++)
            putchar('0');
        printf("%s\n",a);
        return 0;
    }
    

    【讨论】:

    • 我认为这不是我们想要的输出。需要的是 16 个零,然后是来自 a 的 3 个字符。如果a 是一个 10 个字符的字符串,那将是 9 个零和来自a 的 10 个字符。您的代码必须进行调整才能实现。
    • @JonathanLeffler:最初的问题并不清楚。通过在 for 循环的
    • 鉴于示例输出,我不确定有什么不清楚的地方。
    • 要求是在文件中打印日志,其中很少有字段用零填充。上面的代码适用于打印 1 或 2 个变量。我必须使用 spritnf 打印大约 10 个变量
    【解决方案3】:

    正如其他答案中提到的,%0.. 格式字符串修饰符仅适用于数字参数。

    对于字符串,您需要手动创建填充并在实际字符串之前添加适当的长度。

    %.*s 格式字符串修饰符可让您指定字符串的长度。您需要创建一个仅包含 0 的填充字符串

    char test[10] = "abcd";
    char padding[25];
    memset(padding, '0', sizeof(padding));
    printf("%.*s%s", 19-strlen(test), padding , test);
    

    这将产生所需的结果。

    你可以看到Demo here

    由于您的问题是关于sprintf,请澄清这也将与sprintf 以类似的方式工作。只需创建一个大小合适的缓冲区,该缓冲区可以容纳填充、实际字符串和空终止符。

    【讨论】:

    • 我已经从谷歌搜索中知道了这个答案,但正在寻找一些更有效的代码,因为我必须将它用于多个变量。
    • @ajay 即使对于多个变量,您所要做的就是重复printf(或sprint)。 memset 只需执行一次即可重复使用。
    猜你喜欢
    • 2015-10-09
    • 2017-01-17
    • 2021-02-19
    • 1970-01-01
    • 1970-01-01
    • 2014-10-29
    • 2012-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多