Cross-platform compatibility of dprintf
Asked Answered
N

3

5

Linux has this nice function dprintf:

The functions dprintf() and vdprintf() (as found in the glibc2 library) are exact analogues of fprintf() and vfprintf(), except that they output to a file descriptor fd instead of to a given stream.

however as that same source points out:

These functions are GNU extensions, not in C or POSIX. Clearly, the names were badly chosen. Many systems (like MacOS) have incompatible functions called dprintf(), usually some debugging version of printf(), perhaps with a prototype like

void dprintf (int level, const char *format, ...);

where the first parameter is a debugging level (and output is to stderr). Moreover, dprintf() (or DPRINTF) is also a popular macro name for a debugging printf. So, probably, it is better to avoid this function in programs intended to be portable.

My question

How can I make a setup that will safely call the dprintf that I want, if it exists, or fail to compile with some reasonably sane error message if that function doesn't exist? I guess I'd do something like this:

#ifdef SOMETHING
    #define Dprintf dprintf
#else
    #error "no dprintf"
#endif

but I don't know what SOMETHING should be. I guess I could restrict it to just Linux but could I make it looser?

Nannie answered 21/1, 2010 at 22:45 Comment(0)
R
9

Looks like dprintf() is actually in POSIX.1-2008 (with the semantics you want), so you can do this:

#if !defined(__GLIBC__) && _POSIX_C_SOURCE < 200809
#error "dprintf may not exist, or may be wrong"
#endif

__GLIBC__ is defined if you're using a gnu build system. This is probably as safe as you can be without writing a small test program.

Rabelais answered 22/1, 2010 at 0:15 Comment(2)
BTW, it is frequent to see things like (_POSIX_C_SOURCE - 0) < 200809 so that it's still a valid (false) expression even when the macro is undefined or empty.Upon
But if something isn't defined, the pre-processor will substitute 0 for it, so _POSIX_C_SOURCE < 200809 will expand to 0 < 200809 if _POSIX_C_SOURCE is not defined or is 0. For empty macros, you're right.Rabelais
U
4

You could make an autoconf test, if you're using autotools for your buildsystem.

AC_LANG([C])
AC_USE_SYSTEM_EXTENSIONS
AC_ARG_WITH([dprintf],
  [AS_HELP_STRING([--with-dprintf],
    [Assume that dprintf prints to a specified file descriptor])],
  [], [with_dprintf=check])
AS_IF([test "x$with_dprintf" = xcheck],
  [AC_RUN_IFELSE(
    [AC_LANG_PROGRAM([[
        #include <stdio.h>
        #include <string.h>
        int debug_level;
      ]], [[
        char msg[] = "Hello, world!\n";
        char rcv[sizeof(msg)] = "";
        FILE *f = tmpfile();
        int fd = fileno(f);
        debug_level = fd - 1;
        dprintf(fd, "%s", msg);
        fseek(f, 0, SEEK_SET);
        fread(rcv, 1, sizeof(msg), f);
        return strcmp(msg, rcv);
      ]]
    )], [with_dprintf=yes])])
AS_IF([test "x$with_dprintf" = xyes],
  [AC_DEFINE([HAVE_DPRINTF], [1],
    [dprintf prints to a specified file descriptor])])

(Allowing for --with-dprintf or --without-dprintf when cross-compiling, as AC_RUN_IFELSE isn't valid in those cases.)


fdopen isn't in standard C, but it is in POSIX.2 and later. I don't recall any UNIX-like that doesn't have it -- heck, even Windows has it.

int fdprintf(int fd, char *fmt, ...) {
    va_list ap;
    FILE *f = fdopen(fd);
    int rc;

    va_start(ap, &fmt);
    rc = vfprintf(f, fmt, ap);
    fclose(f);
    va_end(ap);
    return rc;
}

Of course, if you know which file descriptor you'll be printing to, just keep the FILE* pointer around instead.

Upon answered 21/1, 2010 at 23:53 Comment(1)
I don't have a build system (OK, I'm using make via the make-remaking-make files thing)Nannie
M
2

An Autoconf test could check whether dprintf(1, "blablabla") writes to stdout or stderr, and whether dprintf(-1, "blablabla") fails in an interesting way or writes to stderr.

If libc gives you with the wrong dprintf(), you could implement it yourself on top of snprintf() (or even better asprintf()) and write(). And then mention your 'dprintf.o' to the linker so it prefers yours to libc's.

If you want a better name, I propose fdprintf().

Mo answered 21/1, 2010 at 23:46 Comment(6)
+1 (even thought I'm not using autoconf, or any configuration for that matter) -- I'm using dprintf in place of asprintf+write because it's used in an error handler and I don't want to have to worry about the heap working.Nannie
@BCS: You don't need to use the heap, your function can allocate the buffer on the stack (either a fixed size buffer, or with alloca())Brisket
@Hasturkun: Fixed size won't work (I don't have a reasonable upper bound on length) and under some error recovery situations, stack space is very tight.Nannie
Implementing it on top of snprintf or asprintf is a bad idea, as these require the string to fit in memory and have failure cases. Instead, use fdopen and then just use fprintf.Lee
@R: To quote Mr. Simpson: "Duh!".Mo
@R Given that it returns a pointer I expect fdopen requires a usable heap. As long as that's a valid assumption, good. If not, we are back to my original objection to asprintf.Nannie

© 2022 - 2024 — McMap. All rights reserved.