0

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);`
Louen
  • 3,617
  • 1
  • 29
  • 49
Fra
  • 393
  • 7
  • 19
  • log10(max value of the type), rounding up. So usually 10. – o11c Jan 09 '19 at 00:04
  • 2
    A signed 4-byte integer can require up to 12-characters to display (includeing the *nul-terminating* char), e.g. `-2147483648`. And that is *without* any LOCALE specific separators. – David C. Rankin Jan 09 '19 at 00:06
  • @DavidC.Rankin - is there a reliable way to read the integer value? – Fra Jan 09 '19 at 00:07
  • Sure, you can read an integer from a file stream with either `scanf/fscanf` (not recommended) or read a complete line containing the integer with `fgets` and then parse with either `sscanf` (OK, but only success/failure reporting) or with `strtol` (full error checking, but you must make sure the value converted is `INT_MIN <= intval <= INT_MAX`) – David C. Rankin Jan 09 '19 at 00:09
  • And, let me clarify -- you can simply 'read' the characters that make up an integer with any input routine and if all you need is a character representation of what the value is, there is no need to convert to an actual integer. You only convert the string representation to an actual integer value (as explained above) -- if needed to perform calculations, etc... You can always use the string for display, etc... – David C. Rankin Jan 09 '19 at 00:16
  • @DavidC.Rankin - how do I handle the 1 byte offset though? I want to step over the first character. Thanks. – Fra Jan 09 '19 at 00:22
  • `&output_buffer[1]` is the same as `output_buffer + 1`; both address the character array starting at the second position in the buffer. However, you can't cast that to an `int` because it is not an `int`. It's a character string which, if printed out, would look like an integer to a human reader. – rici Jan 09 '19 at 00:31
  • Perhaps you should clarify what it is exactly that you are trying to do. – rici Jan 09 '19 at 00:32
  • @DavidC.Rankin - I'm trying to reliably extract a, b, c as separate variables. – Fra Jan 09 '19 at 00:49
  • Well, I wrote an answer for `b` below, if you still have a question about `a` and `c` afterwards, let me know and I'm happy to add to the example. – David C. Rankin Jan 09 '19 at 00:59
  • @Fra - I updated the answer to show how to separate `a, b & c` from the filled `buffer`. – David C. Rankin Jan 09 '19 at 01:08

3 Answers3

3

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, e.g.

    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 (e.g. 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, e.g. buffer[x], or using a pointer, e.g, 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), e.g.

#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, e.g.

    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, e.g. '-' 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, e.g.

    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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • 1
    Glad it helped. There is a lot of learning wrapped up in the short example. Take it piece by piece to understand each part. If you have any question about how a function works, look at the man page, e.g. [strtol(3) - Linux manual page](http://man7.org/linux/man-pages/man3/strtol.3.html). It's not enough to have a cursory understanding of how things work in C, instead, you must have an exact understanding of the details. The man pages are cryptic at first, but provide concise detail on exactly how each function works. – David C. Rankin Jan 09 '19 at 20:41
  • Yes it was a great help. I'm still messing around with format specifiers and snprintf get unsigned chars working. My other post is https://stackoverflow.com/questions/54118934/unsigned-characters-and-sprintf-c it's quite the learning curve. – Fra Jan 09 '19 at 21:54
2

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)
ensc
  • 6,704
  • 14
  • 22
  • This is unclear "some implementations require that the format string is located in read-only memory." --> How does the format read-only attribute relate to `"%n"`dangerousness? – chux - Reinstate Monica Jan 09 '19 at 02:15
  • @chux it is dangerous because `printf()` with `%n` behaves like `scanf()`(writes into memory); glibc tries to mitigate this by requiring read-only mapping when building with `-D_FORTIFY_SOURCE` – ensc Jan 09 '19 at 10:08
0

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

Ass3mbler
  • 3,855
  • 2
  • 20
  • 18