使用printf()打印二进制格式的数字的替代方法。

5

我了解到register_printf_specifier现已被弃用。

我无法再使用C99编译器在www.onlinegdb.com上运行使用register_printf_specifier的代码。

例如,我本来想运行下面的代码,以添加%B格式说明符到printf()函数中,以便以二进制形式打印整数(来自有没有一种printf转换器可以以二进制格式打印?):

/*
 * File:   main.c
 * Author: Techplex.Engineer
 *
 * Created on February 14, 2012, 9:16 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <printf.h>
#include <math.h>
#include <string.h>
#include <stdarg.h>

static int printf_arginfo_M(const struct printf_info *info, size_t n, int *argtypes)
{
    /* "%M" always takes one argument, a pointer to uint8_t[6]. */
    if (n > 0) {
        argtypes[0] = PA_POINTER;
   }
    return 1;
}


static int printf_output_M(FILE *stream, const struct printf_info *info, const void *const *args)
{
    int value = 0;
    int len;

    value = *(int *) (args[0]);

    // Beginning of my code ------------------------------------------------------------
    //char buffer [50] = "";  // Is this bad?
    char* buffer = (char*) malloc(sizeof(char) * 50);
    // char buffer2 [50] = "";  // Is this bad?
    char* buffer2 = (char*) malloc(sizeof(char) * 50);
    int bits = info->width;
    if (bits <= 0)
        bits = 8;  // Default to 8 bits

    int mask = pow(2, bits - 1);
    while (mask > 0) {
        sprintf(buffer, "%s", ((value & mask) > 0 ? "1" : "0"));
        strcat(buffer2, buffer);
        mask >>= 1;
    }
    strcat(buffer2, "\n");
    // End of my code --------------------------------------------------------------
    len = fprintf(stream, "%s", buffer2);
    free (buffer);
    free (buffer2);
    return len;
}

int main(int argc, char** argv)
{
    register_printf_specifier('B', printf_output_M, printf_arginfo_M);

    printf("%4B\n", 65);

    return EXIT_SUCCESS;
}

当我进行操作时,会出现以下情况:

main.c:65:53: warning: passing argument 3 of ‘register_printf_specifier’ from incompatible pointer type [-Wincompatible-pointer-types]
     register_printf_specifier('B', printf_output_M, printf_arginfo_M);
                                                     ^~~~~~~~~~~~~~~~
In file included from main.c:18:0:
/usr/include/printf.h:96:12: note: expected ‘int (*)(const struct printf_info *, size_t,  int *, int *) {aka int (*)(const struct printf_info *, long unsigned int,  int *, int *)}’ but argument is of typeint (*)(const struct printf_info *, size_t,  int *) {aka int (*)(const struct printf_info *, long unsigned int,  int *)}’
 extern int register_printf_specifier (int __spec, printf_function __func,
            ^~~~~~~~~~~~~~~~~~~~~~~~~
main.c:67:15: warning: unknown conversion type character ‘Bin format [-Wformat=]
     printf("%4B\n", 65);
               ^
main.c:67:12: warning: too many arguments for format [-Wformat-extra-args]
     printf("%4B\n", 65);
            ^~~~~~~
000

由于register_printf_specifier现在已弃用,程序员应该使用什么替代品?是创建自己的可变参数printf()函数吗?

附注:下面是更新后的代码,纠正了指出的错误。确保您针对要在二进制中显示的整数类型使用正确的格式说明符(例如,对于char使用%hhB,对于short使用%hB)。您可以使用空格或零填充(例如,%018hB将在二进制中添加2个前导零,因为在我使用的计算机上,shorts的大小为16位)。请注意:确保使用正确的格式说明符!如果不这样做,二进制输出很可能会出错,特别是对于负整数或无符号整数。

/*
 * File:        main.c
 * Author:      Techplex.Engineer
 * Modified by: Robert Kennedy
 *
 * Created on February 14, 2012, 9:16 PM
 * Modified on August 28, 2021, 9:06 AM
 */

//
// The following #pragma's are the only way to supress the compiler warnings
// because there is no way of letting -Wformat know about the 
// custom %B format specifier.
//
#pragma GCC diagnostic ignored "-Wformat="  
#pragma GCC diagnostic ignored "-Wformat-extra-args"

#include <stdio.h>      // Needed for fprintf(); sprintf() and printf();
#include <stdlib.h>     // Needed for exit(); malloc(); and free(); and 
                        // EXIT_SUCCESS macro constant.
#include <printf.h>     // Needed for register_printf_specifier(); and related
                        // data structures (like "const struct print_info") and 
                        // related macro constants (e.g. PA_POINTER)
#include <math.h>       // Needed for pow(); and powl();
#include <string.h>     // Needed for strcat; strncat; memset();
#include <limits.h>     // Needed for min and max values for the various integer
                        // data types.
#include <inttypes.h>   // Needed for int64_t data types and the min and max 
                        // values.

static int printf_arginfo_B(const struct printf_info *info, size_t n, int *argtypes, int* size)
{
    if  (info->is_long_double)
        *size = sizeof(long long);  /* Optional to specify *size here */
    else if  (info->is_long)
        *size = sizeof(long);       /* Optional to specify *size here */
    else
        *size = sizeof(int);        /* Optional to specify *size here */
        
    if (n > 0)                      /* means there are arguments! */
    {
        argtypes[0] = PA_POINTER;   /* Specifies a void* pointer type */
   }
    return 1;
}

static int printf_output_B(FILE *stream, const struct printf_info *info, const void *const *args)
{
    const int sizeOfByte = CHAR_BIT;
    const int charSizeInBits = sizeof(char) * sizeOfByte;
    const int shortSizeInBits = sizeof(short) * sizeOfByte;
    const int intSizeInBits = sizeof(int) * sizeOfByte;
    const int longSizeInBits = sizeof(long) * sizeOfByte;
    const int longlongSizeInBits = sizeof(long long) * sizeOfByte;
    
    unsigned int intValue = 0;
    unsigned long longValue =  0l;
    unsigned long long longlongValue = 0ll;
    
    int len;                    // Length of the string (containing the binary
                                // number) that was printed.
                                // On error, a negative number will be returned.
                                
    int i;                      // A simple counter variable.
    
    int displayBits;            // Number of bits to be displayed
                                // If greater than calcBits, leading zeros
                                // will be displayed.
                                
    int calcBits;               // The minimum number of bits needed for the 
                                // decimcal to binary conversion.
    
    displayBits = info->width;
    wchar_t padWithZero = info->pad;
    char padChar = ' ';
    
    if (info->is_long_double) 
    {
        calcBits = longlongSizeInBits;
        if (displayBits < longlongSizeInBits)
        {
            displayBits = longlongSizeInBits;
        }
    }
    
    if (info->is_long) 
    {
        calcBits = longSizeInBits;
        if (displayBits < longSizeInBits)
        {
            displayBits = longSizeInBits;
        }
    }
    
    if ( !(info->is_long) && !(info->is_long_double) && !(info->is_short) && !(info->is_char) )
    {
        calcBits = intSizeInBits;
        if (displayBits < intSizeInBits)
        {
            displayBits = intSizeInBits;
        }
    }
    
    if (info->is_short)
    {
        calcBits = shortSizeInBits;
        if (displayBits < shortSizeInBits)
        {
            displayBits = shortSizeInBits;
        }
    }
    
    if (info->is_char)
    {
        calcBits = charSizeInBits;
        if (displayBits < charSizeInBits)
        {
            displayBits = charSizeInBits;
        }
    }
    
    // printf("\ndisplayBits = %d and calcBits = %d\n", displayBits, calcBits);
    
    char* buffer = (char*) malloc(sizeof(char) * (displayBits+1));
    char* buffer2 = (char*) malloc(sizeof(char) * (displayBits+1));
    
    if ( info->is_long_double )
    {
        longlongValue= * ((unsigned long long *) (args[0]));
        unsigned long long mask = powl(2, calcBits - 1);
        while (mask > 0) 
        {
            sprintf(buffer, "%s", ((longlongValue & mask) > 0 ? "1" : "0"));
            // strcat(buffer2, buffer);
            strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
            mask >>= 1;
        }
    }
    else if ( info->is_long )
    {
        longValue= * ((unsigned long *) (args[0]));
        unsigned long mask = powl(2, calcBits - 1);
        while (mask > 0) 
        {
            sprintf(buffer, "%s", ((longValue & mask) > 0 ? "1" : "0"));
            // strcat(buffer2, buffer);
            strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
            mask >>= 1;
        }
    }
    else
    {
        intValue = * ((unsigned int *) (args[0]));
        unsigned long mask = pow(2, calcBits - 1);
        while (mask > 0) {
        sprintf(buffer, "%s", ((intValue & mask) > 0 ? "1" : "0"));
        // strcat(buffer2, buffer);
        strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
        mask >>= 1;
        }
    }
    
    strcat(buffer2, "\0");
    
    if (displayBits > calcBits)
    {
        if ('0' == padWithZero) 
            padChar = '0';
        else
            padChar = ' ';
        memset(buffer, '\0', displayBits);
        memset(buffer, padChar, (displayBits-calcBits));
        strncat(buffer, buffer2, displayBits-( (int)strlen(buffer)) );
        len = fprintf(stream, "%s", buffer);
    }
    else
    {
        len = fprintf(stream, "%s", buffer2);
    }
    
    free (buffer);
    free (buffer2);
    
    return len;
}

int main(int argc, char** argv)
{
    const int sizeOfByte = 8;
    
    register_printf_specifier('B', printf_output_B, printf_arginfo_B);

    printf("Sizeof(char) is: %ld bits\n", sizeof(char) * sizeOfByte);
    printf("CHAR_MAX %hhd in binary is: %hhB\n", CHAR_MAX, CHAR_MAX);
    printf("CHAR_MIN %hhd in binary is: %hhB\n",  CHAR_MIN, CHAR_MIN);
    printf("UCHAR_MAX %hhu (unsigned) in binary is: %hhB\n", UCHAR_MAX, UCHAR_MAX);
    printf("%hhd in binary is: %hhB\n",  -5, -5);
    printf(" %hhd in binary is: %hhB\n\n",  0, 0);
    
    printf("Sizeof(short) is: %ld bits\n", sizeof(short) * sizeOfByte);
    printf("SHRT_MAX  %hd in binary is: %hB\n", SHRT_MAX, SHRT_MAX);
    printf("SHRT_MIN %hd in binary is: %hB\n", SHRT_MIN, SHRT_MIN);
    printf("USHRT_MAX %hu (unsigned) in binary is: %hB\n", USHRT_MAX, USHRT_MAX);
    printf("USHRT_MAX %hu (unsigned) in binary with 2 leading  zeros is: %018hB\n", USHRT_MAX, USHRT_MAX);
    printf("USHRT_MAX %hu (unsigned) in binary with 2 leading spaces is: %18hB\n\n", USHRT_MAX, USHRT_MAX);
    
    printf("Sizeof(int) is: %ld bits\n", sizeof(int) * sizeOfByte);
    printf("INT_MAX  %d in binary is: %B\n",  INT_MAX, INT_MAX);
    printf("INT_MIN %d in binary is: %B\n",  INT_MIN, INT_MIN);
    printf("UINT_MAX %u (unsigned) in binary is: %B\n",  UINT_MAX, UINT_MAX);
    printf("UINT_MAX %u (unsigned) in binary with 4 leading zeros is: %036B\n\n",  UINT_MAX, UINT_MAX);
    
    printf("Sizeof(long) is: %ld bits\n", sizeof(long) * sizeOfByte);
    printf("LONG_MAX  %ld in binary is: %lB\n", LONG_MAX, LONG_MAX);
    printf("LONG_MIN %ld in binary is: %lB\n", LONG_MIN, LONG_MIN);
    printf("ULONG_MAX %lu (unsigned) in binary is: %lB\n\n", ULONG_MAX, ULONG_MAX);
    
    printf("Sizeof(long long) is: %ld bits\n", sizeof(long long) * sizeOfByte);
    printf("LLONG_MAX  %lld in binary is: %llB\n", LLONG_MAX, LLONG_MAX);
    printf("LLONG_MIN %ld in binary is: %lB\n", LLONG_MIN, LLONG_MIN);
    printf("ULLONG_MAX %llu (unsigned) in binary is: %llB\n\n", ULLONG_MAX, ULLONG_MAX);
    
    printf("Sizeof(int64_t) is: %ld bits\n", sizeof(int64_t) * sizeOfByte);
    printf("INT_64_MAX  %lld in binary is: %LB\n", INT64_MAX, INT64_MAX);
    printf("INT_64_MIN %lld in binary is: %LB\n", INT64_MIN, INT64_MIN);
    printf("UINT64_MAX %llu in binary is: %LB\n", UINT64_MAX, UINT64_MAX);
    
    return EXIT_SUCCESS;
}

以下是输出结果:
Sizeof(char) is: 8 bits
CHAR_MAX 127 in binary is: 01111111
CHAR_MIN -128 in binary is: 10000000
UCHAR_MAX 255 (unsigned) in binary is: 11111111
-5 in binary is: 11111011
 0 in binary is: 00000000

Sizeof(short) is: 16 bits
SHRT_MAX  32767 in binary is: 0111111111111111
SHRT_MIN -32768 in binary is: 1000000000000000
USHRT_MAX 65535 (unsigned) in binary is: 1111111111111111
USHRT_MAX 65535 (unsigned) in binary with 2 leading  zeros is: 001111111111111111
USHRT_MAX 65535 (unsigned) in binary with 2 leading spaces is:   1111111111111111

Sizeof(int) is: 32 bits
INT_MAX  2147483647 in binary is: 01111111111111111111111111111111
INT_MIN -2147483648 in binary is: 10000000000000000000000000000000
UINT_MAX 4294967295 (unsigned) in binary is: 11111111111111111111111111111111
UINT_MAX 4294967295 (unsigned) in binary with 4 leading zeros is: 000011111111111111111111111111111111

Sizeof(long) is: 64 bits
LONG_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
LONG_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
ULONG_MAX 18446744073709551615 (unsigned) in binary is: 1111111111111111111111111111111111111111111111111111111111111111

Sizeof(long long) is: 64 bits
LLONG_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
LLONG_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
ULLONG_MAX 18446744073709551615 (unsigned) in binary is: 1111111111111111111111111111111111111111111111111111111111111111

Sizeof(int64_t) is: 64 bits
INT_64_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
INT_64_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
UINT64_MAX 18446744073709551615 in binary is: 1111111111111111111111111111111111111111111111111111111111111111

2
明确地说,register_printf_specifierregister_printf_function都不是标准C中定义的函数。它们似乎是GNU libc的扩展。 - Keith Thompson
1个回答

8

register_printf_specifier并没有被弃用 - register_printf_function已经被弃用。你的代码可以编译和运行 - 你可以看到它在结尾处打印了二进制 - 只是有一些编译器警告。如果你需要证明register_printf_specifier是有效的,请参见printf.h:

typedef int printf_arginfo_size_function (const struct printf_info *__info,
                                          size_t __n, int *__argtypes,
                                          int *__size);
/* Old version of 'printf_arginfo_function' without a SIZE parameter.  */
typedef int printf_arginfo_function (const struct printf_info *__info,
                                     size_t __n, int *__argtypes);

...

/* Register FUNC to be called to format SPEC specifiers; ARGINFO must be
   specified to determine how many arguments a SPEC conversion requires and
   what their types are.  */
extern int register_printf_specifier (int __spec, printf_function __func,
                                      printf_arginfo_size_function __arginfo)
  __THROW;
/* Obsolete interface similar to register_printf_specifier.  It can only
   handle basic data types because the ARGINFO callback does not return
   information on the size of the user-defined type.  */
extern int register_printf_function (int __spec, printf_function __func,
                                     printf_arginfo_function __arginfo)
  __THROW __attribute_deprecated__;

在现代代码中,不应使用register_printf_function,而应该使用register_printf_specifier。你会发现它们的签名非常相似,唯一的区别在于最后一个参数是printf_arginfo_size_function而不是printf_arginfo_function
这就是你的问题——你正在将printf_arginfo_function类型的参数传递给期望printf_arginfo_size_function类型参数的函数。你需要向printf_arginfo_M添加一个int* size参数,并用你的参数大小填充它——即*size = sizeof(int)
顺便说一下,你可以从编译器警告中理解这一点:
/usr/include/printf.h:96:12:注意:期望的是“int (*)(const struct printf_info *,size_t,int *,int) {aka int (*)(const struct printf_info *,long unsigned int,int *)}”,但实际参数类型为“int (*)(const struct printf_info *,size_t,int *) {aka int (*)(const struct printf_info *,long unsigned int,int *)}”。

关于您的修饰函数,请考虑您的代码:

int bits = info->width;
if (bits <= 0)
    bits = 8;  // Default to 8 bits

int mask = pow(2, bits - 1);

考虑到你这里缺少了一些位数 - 例如对于数字65,你需要7位,而当你打印“%4B”时,你只打印了底部的4位。
另外,如果你想要以二进制形式打印64位数字,那么就像你使用%lld打印64位整数一样 - 你应该使用%llB打印数字。然后,在printf_output_m中,你可以编写以下代码:
if (info->is_long_double) {
    long_value = *(uint64_t*)(args[0]);
    bits = 64;
}

请注意,这需要对您的函数进行全面重新设计——您必须将maxBits更改为64(因为您想支持最多打印64位),等等。
关于 main.c:67:15: warning: unknown conversion type character ‘B’ in format [-Wformat=] 警告 - 这些警告是无法避免的,除非 手动抑制你的代码行中的-Wformat标志。目前为止,没有办法让 -Wformat 知道你的自定义说明符。

无关的侧面说明 - Google最新的CTF比赛中有一个非常酷的挑战,涉及使用已弃用的register_printf_function编写的虚拟机进行反向工程 - 您可以在此处看到源代码。


非常感谢您的积极回应,我们非常感激。您提到:“您需要在printf_output_M()中添加一个int* size参数..”,我尝试了一下,但是出现了更多的编译器警告信息。我猜您的意思是“您需要在printf_arginfo_M()的参数末尾添加一个int* size参数..”。一旦我这样做了,编译器警告就消失了。但是我仍然对您的评论“将其填充为参数的大小-即*size = sizeof(int)”感到困惑。这应该是printf_arginfo_M()内部的语句吗? - RobK
再次感谢您的帮助和深入见解!我已经纠正了您指出的代码错误。(请参见我上面问题的结尾以获取更新后的代码)。它可以很好地处理字符、短整型和整型。但是对于超过32位的数字(例如长整型),它无法正常工作。我仍在努力找出原因。 - RobK
我已经编辑了我的答案,以解决64位问题。至于 *size = sizeof(int) - 是的,它应该在 printf_arginfo_M 中。这并不是格式说明符正常工作所必需的。 - Daniel Kleinstein
非常感谢您的帮助。我已经使用您的输入更新和纠正了代码(请参见上面的问题)。新添加的“%B”printf()格式说明符(使用register_printf_specifier()函数)现在似乎运行良好。代码甚至似乎可以正确地以二进制形式显示负数和无符号数。我学到了很多!再次感谢! - RobK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接