Discussion:
Something like string-streams existing in "C"?
(too old to reply)
Janis Papanagnou
2024-12-19 01:30:18 UTC
Permalink
Inspecting some of my "C" source code here I noticed a construct that
I dislike...

static char buf[32]; // sufficient space for ansi sequence
...
sprintf (buf, "\033[38;5;%dm%c\033[0m", ...);

In case of known or deducible string sizes I'm preferring and using
some of the alloc() functions. In the sample I can at least deduce an
upper-bound for the buffer-size. But it's anyway still an undesired
hard-coded buffer size that I'd like to avoid.

I recall that in C++ I used "String-Streams" for dynamically extended
strings. But in "C" my reflex (and habit) is to write things like the
above code.

Is there something in "C" that allows dynamic flexibility of strings?
(Or are there other options that an experienced "C" programmer would
use instead?)

(If there's something available only with newer C-standards I'd also
appreciate a hint.)

Janis
Keith Thompson
2024-12-19 02:14:22 UTC
Permalink
Post by Janis Papanagnou
Inspecting some of my "C" source code here I noticed a construct that
I dislike...
static char buf[32]; // sufficient space for ansi sequence
...
sprintf (buf, "\033[38;5;%dm%c\033[0m", ...);
In case of known or deducible string sizes I'm preferring and using
some of the alloc() functions. In the sample I can at least deduce an
upper-bound for the buffer-size. But it's anyway still an undesired
hard-coded buffer size that I'd like to avoid.
I recall that in C++ I used "String-Streams" for dynamically extended
strings. But in "C" my reflex (and habit) is to write things like the
above code.
Is there something in "C" that allows dynamic flexibility of strings?
(Or are there other options that an experienced "C" programmer would
use instead?)
(If there's something available only with newer C-standards I'd also
appreciate a hint.)
You can use snprintf() for this. If the size argument is too small, it
still returns the number of characters (excluding the terminating '\0')
that would have been written to the buffer.

char *buf;
int arg1 = 42;
int arg2 = 'x';
int size = snprintf(buf, 0, "\033[38;5;%dm%c\033[0m", arg1, arg2);
printf("Allocating %d bytes\n", size + 1);
buf = malloc(size + 1); // error checking skipped
int actual_size = snprintf(buf, size + 1, "\033[38;5;%dm%c\033[0m", arg1, arg2);
printf("actual_size = %d, buf = \"%s\"\n", actual_size, buf);

Note that the output includes non-printing characters, so you'll want to
pipe it throught `cat -A` or something similar.

Beware that the size argument includes the terminating null byte, but
the value returned does not (it returns the length of the resulting
string, not its size).

GNU and BSD provide a non-standard asprintf() function that lets you do
this in one step.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Lawrence D'Oliveiro
2024-12-19 04:41:25 UTC
Permalink
Post by Janis Papanagnou
Is there something in "C" that allows dynamic flexibility of strings?
As usual, standard C is pretty boring in this regard. And as usual, POSIX
offers something a bit more.

<https://manpages.debian.org/3/open_wmemstream.3.en.html>
BlueManedHawk
2024-12-19 13:32:27 UTC
Permalink
The ‘asprintf’ subroutine is standardized by POSIX.1-2024, meaning that
you can use it now and blame somebody else if it doesn't work. If you
can't target POSIX, the subroutine is also _theoretically_ available
through the feature-test macro ‘__STDC_WANT_LIB_EXT2__’, assuming that
‘__STDC_ALLOC_LIB__’ is a predefined macro, but gLibC does not pay
attention to that for this subroutine, and which feature-test macro does
induce its declaration is seemingly undocumented, though it is at least
defined through ‘_DEFAULT_SOURCE’.
Kaz Kylheku
2024-12-19 19:47:28 UTC
Permalink
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024, meaning that
you can use it now and blame somebody else if it doesn't work. If you
Regardless of how it is made visible, you can detect it via a compile
test in a configure script, and provide your own if it wasn't found:

#if !HAVE_ASPRINTF

int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc and
vsprintf
}

#endif

BTW, is there no wchar_t version of this?
Post by BlueManedHawk
can't target POSIX, the subroutine is also _theoretically_ available
through the feature-test macro ‘__STDC_WANT_LIB_EXT2__’, assuming that
‘__STDC_ALLOC_LIB__’ is a predefined macro, but gLibC does not pay
When would it be the case that you can't target POSIX, but *can* mess
around with some the internal feature test macros of some specific POSIX
vendor? :)
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
Michael S
2024-12-19 20:04:35 UTC
Permalink
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024, meaning
that you can use it now and blame somebody else if it doesn't work.
If you
Regardless of how it is made visible, you can detect it via a compile
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc and
vsprintf
Don't you mean, vsnprintf ?
Post by Kaz Kylheku
}
#endif
BTW, is there no wchar_t version of this?
Post by BlueManedHawk
can't target POSIX, the subroutine is also _theoretically_
available through the feature-test macro ‘__STDC_WANT_LIB_EXT2__’,
assuming that ‘__STDC_ALLOC_LIB__’ is a predefined macro, but gLibC
does not pay
When would it be the case that you can't target POSIX, but *can* mess
around with some the internal feature test macros of some specific
POSIX vendor? :)
Kaz Kylheku
2024-12-19 22:06:09 UTC
Permalink
Post by Michael S
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024, meaning
that you can use it now and blame somebody else if it doesn't work.
If you
Regardless of how it is made visible, you can detect it via a compile
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc and
vsprintf
Don't you mean, vsnprintf ?
That detail will become obvious when you try to implement it.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
Thiago Adams
2024-12-20 02:14:17 UTC
Permalink
Post by Kaz Kylheku
Post by Michael S
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024, meaning
that you can use it now and blame somebody else if it doesn't work.
If you
Regardless of how it is made visible, you can detect it via a compile
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc and
vsprintf
Don't you mean, vsnprintf ?
That detail will become obvious when you try to implement it.
I did on implementation in 2020 (not using it)

http://thradams.com/vadsprintf.html


The standard should have a string stream compatible with FILE because
- differently of asprintf - if cannot be implemented separately.
Michael S
2024-12-20 11:00:56 UTC
Permalink
On Thu, 19 Dec 2024 23:14:17 -0300
Post by Thiago Adams
Post by Kaz Kylheku
Post by Michael S
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024,
meaning that you can use it now and blame somebody else if it
doesn't work. If you
Regardless of how it is made visible, you can detect it via a
compile test in a configure script, and provide your own if it
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc
and vsprintf
Don't you mean, vsnprintf ?
That detail will become obvious when you try to implement it.
I did on implementation in 2020 (not using it)
http://thradams.com/vadsprintf.html
You mean, you don't use asprintf() that you implemented?
That's understandable. The API is rather badly designed. Can be handy
in toy examples, less so in production software.
Post by Thiago Adams
The standard should have a string stream compatible with FILE because
- differently of asprintf - if cannot be implemented separately.
What level of compatibility?
IMHO, the level that makes sense is where compatibility excludes
fopen, fclose and fflush. I.e. you have new functions, mem_fopen()
and mem_fclose() and do not allow fflush(). Pluse, you add few more
functions or macros for direct access to buffer.
Thiago Adams
2024-12-20 11:32:24 UTC
Permalink
Post by Michael S
On Thu, 19 Dec 2024 23:14:17 -0300
Post by Thiago Adams
Post by Kaz Kylheku
Post by Michael S
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024,
meaning that you can use it now and blame somebody else if it
doesn't work. If you
Regardless of how it is made visible, you can detect it via a
compile test in a configure script, and provide your own if it
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc
and vsprintf
Don't you mean, vsnprintf ?
That detail will become obvious when you try to implement it.
I did on implementation in 2020 (not using it)
http://thradams.com/vadsprintf.html
You mean, you don't use asprintf() that you implemented?
That's understandable. The API is rather badly designed. Can be handy
in toy examples, less so in production software.
For my needs a string stream is better I am using this
https://github.com/thradams/cake/blob/main/src/osstream.c
Unfortunately it is not compatible with FILE*.
Post by Michael S
Post by Thiago Adams
The standard should have a string stream compatible with FILE because
- differently of asprintf - if cannot be implemented separately.
What level of compatibility?
IMHO, the level that makes sense is where compatibility excludes
fopen, fclose and fflush. I.e. you have new functions, mem_fopen()
and mem_fclose() and do not allow fflush(). Pluse, you add few more
functions or macros for direct access to buffer.
A function printing in a FILE* fprint also should be able to print in a
string stream.
Michael S
2024-12-20 12:55:41 UTC
Permalink
On Fri, 20 Dec 2024 08:32:24 -0300
Post by Thiago Adams
A function printing in a FILE* fprint also should be able to print in
a string stream.
Of course.
But I don't like your naming and semantics implied by the name.
It should be memory buffer stream rather than string stream.
I.e. zero characters should be allowed in the middle, zero termination
not guaranteed, fwrite and fputc should work as expected etc...
Lawrence D'Oliveiro
2024-12-20 20:39:12 UTC
Permalink
Post by Thiago Adams
For my needs a string stream is better I am using this
https://github.com/thradams/cake/blob/main/src/osstream.c Unfortunately
it is not compatible with FILE*.
As usual, standard C is pretty boring in this regard. And as usual, POSIX
offers something a bit more.

<https://manpages.debian.org/3/open_wmemstream.3.en.html>

Keith Thompson
2024-12-20 18:45:01 UTC
Permalink
Post by Michael S
On Thu, 19 Dec 2024 23:14:17 -0300
[...]
Post by Michael S
Post by Thiago Adams
The standard should have a string stream compatible with FILE because
- differently of asprintf - if cannot be implemented separately.
What level of compatibility?
IMHO, the level that makes sense is where compatibility excludes
fopen, fclose and fflush. I.e. you have new functions, mem_fopen()
and mem_fclose() and do not allow fflush(). Pluse, you add few more
functions or macros for direct access to buffer.
POSIX defines fmemopen(), similar to fopen().

FILE *fmemopen(void *restrict buf, size_t max_size, const char *restrict mode);
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Michael S
2024-12-20 10:38:40 UTC
Permalink
On Thu, 19 Dec 2024 22:06:09 -0000 (UTC)
Post by Kaz Kylheku
Post by Michael S
On Thu, 19 Dec 2024 19:47:28 -0000 (UTC)
Post by Kaz Kylheku
Post by BlueManedHawk
The ‘asprintf’ subroutine is standardized by POSIX.1-2024,
meaning that you can use it now and blame somebody else if it
doesn't work. If you
Regardless of how it is made visible, you can detect it via a
compile test in a configure script, and provide your own if it
#if !HAVE_ASPRINTF
int asprintf(char **out, const char *fmt, ...)
{
... // more or less trivial to implement using malloc, realloc
and vsprintf
Don't you mean, vsnprintf ?
That detail will become obvious when you try to implement it.
It sounds like Janis would prefer different API.

struct string_buffer {
char* ptr;
size_t len;
size_t cap;
};
int append_printf(struct string_buffer*, , const char *fmt, ...);

Implementation is as trivial as asprintf.
Scott Lurndal
2024-12-19 13:53:21 UTC
Permalink
Post by Janis Papanagnou
Inspecting some of my "C" source code here I noticed a construct that
I dislike...
static char buf[32]; // sufficient space for ansi sequence
...
sprintf (buf, "\033[38;5;%dm%c\033[0m", ...);
snprintf is much safer for cases like this, as it will detect
overflow and terminate the formatting before overrunning the buffer.
Janis Papanagnou
2024-12-20 02:56:34 UTC
Permalink
[...]
Thanks for the various suggestions and hints. I've got an idea
what's possible and what I'll buy with each option. That's very
useful information.

Janis
Loading...