Reputation: 423
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);`
Upvotes: 0
Views: 539
Reputation: 6984
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)
Upvotes: 2
Reputation: 84561
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.
Upvotes: 3
Reputation: 3915
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
Upvotes: 0