简体   繁体   中英

C Format Specifier Sizes

How do I access an integer value in a character array?

char a = 'A';
int b = 90;
char * c = "A String";
snprintf(output_buffer, 1024, "%c%d%s, a,b,c);`

How many bytes will the %d format specifier (I assume 4?) take up in this character array and how do I access the integer value b?

I've tried:

int x = *((int *) &output_buffer[1]

without success.

short x; 
sscanf(&output_buffer[1], "%d", &x);
printf("%d\n",x);`

char *buffer; 
sscanf(&output_buffer[3], "%s", buffer);
printf("%s\n",buffer);`

In answer to your original question, "how many characters will "%d" take?", you can use a trick with snprintf by specifying the buffer as NULL and the number of characters as 0 , and then using your "%d" format string and your variable b , snprintf will return the number of digits the conversion will require, eg

    req = snprintf (NULL, 0, "%d", b);
    printf ("required digits: %d\n", req);

Which will output "required digits: 2" . ( "the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available." ) Which is useful when dynamically allocating storage for buffer . In fact, you simply provide your full format string and all variables and snprintf will return the total number of characters needed (to which you add +1 for the nul-terminating character)

From the last few comments, I take it you want to read 90 back into an int from within buffer . That is simple enough to do.

Rather than simply attempting to convert by using the 2nd character in buffer (eg buffer[1] ), for the generic case, you simply want to start with the 1st character in buffer and scan forward until you find the first digit. (you can also check for '+/-' if you have explicit signed values).

To scan forward in buffer to find the first digit, you iterate over the characters in buffer (either using indexes, eg buffer[x] , or using a pointer, eg, char *p = buffer; and incrementing p++ ) and check whether each character is a digit. While you can simply use if ('0' <= *p && *p <= '9') , the ctype.h header provides the isdigit() macro that makes this quite easy. To find the first digit, you could do something like:

#include <ctype.h>  /* for isdigit */
...
char buffer[MAXC] = "",  /* buffer to hold a, b, c */
    *p = buffer;
...
    while (*p && !isdigit(*p))  /* scan forward in buffer to 1st digit */
        p++;

Once you have found your first digit, you convert the sequence of digits to a long value using strtol (which provides full error checking), eg

#include <stdlib.h>
#include <errno.h>  /* for errno   */
#include <limits.h> /* for INT_MIN/INT_MAX */
...
    char *endptr;           /* end pointer to use with strtol */
    long tmp;               /* long value for return of strtol */
    ...
    errno = 0;                      /* reset errno - to check after strtol */
    tmp = strtol (p, &endptr, 0);   /* save conversion result in tmp */

( note: avoid atoi() is provides zero error checking of the conversion)

Now tmp holds the return of strtol which will contain a conversion to long of the digits found in buffer beginning at p (on success). But, before you can make use of the value returned as an int , you must validate that digits were converted, that no error occurred within the conversion, and that the value in tmp is within the range of int . You can do all with a few conditional checks. If all your checks are satisfied, you can then assign the value in tmp to an integer (with the appropriate cast) and have confidence in your final value, eg

    if (p == endptr)                /* check if pointer == end pointer */
        fputs ("error: no digits converted.\n", stderr);
    else if (errno)             /* if errno set, over/underflow occurred */
        fputs ("error: invalid conversion to long.\n", stderr);
    else if (tmp < INT_MIN || INT_MAX < tmp)    /* will fit in int? */
        fputs ("error: value exceeds range of int.\n", stderr);
    else {      /* good value, assign to b_from_buf, output */
        b_from_buf = (int)tmp;
        printf ("\nint read from buffer: %d\n", b_from_buf);
    }

Putting your example together (and including validation of your original write to buffer with snprintf , you could do something similar to the following):

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>  /* for isdigit */
#include <errno.h>  /* for errno   */
#include <limits.h> /* for INT_MIN/INT_MAX */

#define MAXC 1024

int main (void) {

    char a = 'A',
        *c = "A String",
        buffer[MAXC] = "",  /* buffer to hold a, b, c */
        *p = buffer,        /* pointer to buffer */
        *endptr;            /* end pointer to use with strtol */
    int b = 90,
        b_from_buf,         /* int to read from filled buffer */
        rtn;                /* return for snprintf to validate */
    long tmp;               /* long value for return of strtol */

    rtn = snprintf (buffer, MAXC, "%c%d%s", a, b, c);
    if (rtn < 0) {  /* if < 0, error occurred */
        fputs ("error: writing to buffer.\n", stderr);
        return 1;
    }
    else if (rtn >= MAXC)   /* if > size, truncation occurred */
        fputs ("warning: buffer contains truncated string.\n", stderr);

    printf ("%s\n", buffer);    /* output buffer */

    while (*p && !isdigit(*p))  /* scan forward in buffer to 1st digit */
        p++;

    errno = 0;                      /* reset errno - to check after strtol */
    tmp = strtol (p, &endptr, 0);   /* save conversion result in tmp */
    if (p == endptr)                /* check if pointer == end pointer */
        fputs ("error: no digits converted.\n", stderr);
    else if (errno)             /* if errno set, over/underflow occurred */
        fputs ("error: invalid conversion to long.\n", stderr);
    else if (tmp < INT_MIN || INT_MAX < tmp)    /* will fit in int? */
        fputs ("error: value exceeds range of int.\n", stderr);
    else {      /* good value, assign to b_from_buf, output */
        b_from_buf = (int)tmp;
        printf ("\nint read from buffer: %d\n", b_from_buf);
    }
}

( note: if the value in buffer can have an explicit sign before it, eg '-' or '+' , then you add those to the same conditional with isdigit() )

Example Use/Output

$ ./bin/snprintf_string
A90A String

int read from buffer: 90

After Last Comment Wanting 'a, b & c` Back

You already have all you need to get a, b & c back from buffer. Since you used strtol and endptr will point to the next character after the last digit converted, you can get a, b & c back from buffer by simply outputting the values, eg

    else {      /* good value, assign to b_from_buf, output */
        b_from_buf = (int)tmp;
        printf ("\n(a) 1st char in buffer        : %c\n"
                "(b) int read from buffer      : %d\n"
                "(c) remaining chars in buffer : %s\n", 
                *buffer, b_from_buf, endptr);
    }

Modified Example Use/Output

$ ./bin/snprintf_string
A90A String

(a) 1st char in buffer        : A
(b) int read from buffer      : 90
(c) remaining chars in buffer : A String

Look things over and let me know if you have further questions.

There exists a %n modifiers which stores the actual number of written bytes into an int :

int main(int argc, char *argv[])
{
    int p0;
    int p1;
    char    buf[128];

    sprintf(buf, "%c%n%d%n%s", argv[0][0], &p0, atoi(argv[1]), &p1, argv[1]);
    printf("'%s' -> %d, %d\n", buf, p0, p1);
}

But this modifier is considered dangerous; some implementations require that the format string is located in read-only memory.

To make the last sentence more clear, an example:

#include <stdio.h>

int main(void)
{
        char    fmt[] = "%n";
        int     pos;

        printf("%n", &pos);
        printf("ok\n");

        printf(fmt, &pos);
        printf("ok\n");
}

and then

$ gcc x.c -D_FORTIFY_SOURCE=2 -O2
$ ./a.out 
ok
*** %n in writable segment detected ***
Aborted (core dumped)

In your example, assuming the character array you are asking about is output_buffer and that the size of char is 1 byte in your architecture, the %d will take 2 bytes, one for each digit of your int (b = 90). To get back the value, use:

int x; 
sscanf(&output_buffer[1], "%d", &x);
char buffer[255]; 
sscanf(&output_buffer[3], "%[^\n]", buffer);

Please, check the size of buffer to avoid overflows

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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