7

I have a struct that contains a string and a length:

typedef struct string {
  char* data;
  size_t len;
} string_t;

Which is all fine and dandy. But, I want to be able to output the contents of this struct using a printf-like function. data may not have a nul terminator (or have it in the wrong place), so I can't just use %s. But the %.*s specifier requires an int, while I have a size_t.

So the question now is, how can I output the string using printf?

Jeremy Rodi
  • 2,485
  • 2
  • 21
  • 40
  • 7
    You can *convert* a `size_t` to an `int`, provided the value fits... – Kerrek SB Oct 02 '13 at 20:13
  • 3
    @KerrekSB Well if the length doesn't fit in an `int` that's going to be one interesting `printf` call :-)) Should check though since it could overflow. – cnicutar Oct 02 '13 at 20:13
  • 6
    If `data` might contain non-printable characters (like a null character), you don't want `%s` at all. Write a loop. – Carl Norum Oct 02 '13 at 20:13

3 Answers3

14

Assuming that your string doesn't have any embedded NUL characters in it, you can use the %.*s specifier after casting the size_t to an int:

string_t *s = ...;
printf("The string is: %.*s\n", (int)s->len, s->data);

That's also assuming that your string length is less than INT_MAX. If you have a string longer than INT_MAX, then you have other problems (it will take quite a while to print out 2 billion characters, for one thing).

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • not very likely, but the OP *could* be targeting an embedded or historic device with 16-bit `int` – user4815162342 Oct 02 '13 at 20:26
  • Is this really the best way to go about doing this? When I first noted the behavior, my first question was "Why isn't it a `size_t` anyway?" – Jeremy Rodi Oct 02 '13 at 20:28
  • 1
    @JeremyRodi: Not sure, but my guess is that it uses `int` instead of `size_t` because it's for backwards compatibility with code that was written before the C language was standardized, before the type `size_t` even existed. – Adam Rosenfield Oct 02 '13 at 20:43
4

A simple solution would just be to use unformatted output:

fwrite(x.data, 1, x.len, stdout);
This is actually bad form, since `fwrite` may not write everything, so it should be used in a loop;
for (size_t i, remaining = x.len;
     remaining > 0 && (i = fwrite(x.data, 1, remaining, stdout)) > 0;
     remaining -= i) {
}

(Edit: fwrite does indeed write the entire requested range on success; looping is not needed.)

Be sure that x.len is no larger than SIZE_T_MAX.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • But if I wanted to use something like snprintf, this wouldn't work as well. While in that case I could just copy over the data, I'd rather be able to use other formatting specifiers (like `%i`). – Jeremy Rodi Oct 02 '13 at 20:27
  • @drderp: I don't understand. How would you use `snprintf`? You can use `memcpy` to write to a memory location rather than a file... – Kerrek SB Oct 02 '13 at 20:28
  • @KerrekSB if I wanted to create a string along with a number, I'd have to first use `memcpy` to copy over the string, and then use `snprintf` to output the number at the location I want. Not as great as just doing it all in one go, I think... – Jeremy Rodi Oct 02 '13 at 20:30
  • @drderp: Yeah. Well, it depends whether you want to handle arbitrary data with null bytes. If not, formatted printing with `%.*s` is preferable. – Kerrek SB Oct 02 '13 at 20:31
  • @KerrekSB Ok, one more question: does `%.*s` ignore null bytes? – Jeremy Rodi Oct 02 '13 at 20:38
  • @JeremyRodi: No, printing *stops* at the first null byte. – Kerrek SB Oct 02 '13 at 20:54
  • @KerrekSB Maybe `SIZE_MAX` rather than `SIZE_T_MAX` – chux - Reinstate Monica Oct 02 '13 at 22:06
  • @chux: Maybe. I couldn't actually find it, nor did I look particularly hard. It's `(size_t)-1` in any case. – Kerrek SB Oct 02 '13 at 22:07
  • @KerrekSB Suggest instead SIZE_MAX is >= 65535 and an implementation-defined value. – chux - Reinstate Monica Oct 02 '13 at 22:11
  • "`fwrite` may not write everything, so it should be used in a loop..." — according to https://stackoverflow.com/a/9389745 this is wrong. – mk12 Dec 28 '20 at 21:56
  • @mk12: You're right. I must have been confusing `fwrite` and Posix `write`. Let me strike out that second part. Thanks! – Kerrek SB Dec 29 '20 at 00:34
1

how can I output the string using printf?

In a single call? You can't in any meaningful way, since you say you might have null terminators in strange places. In general, if your buffer might contain unprintable characters, you'll need to figure out how you want to print (or not) those characters when outputting your string. Write a loop, test each character, and print it (or not) as your logic dictates.

Carl Norum
  • 219,201
  • 40
  • 422
  • 469