cvariadic-functionserlang-driver

C variadic function behavior differs from expected equivalent code in an Erlang driver


I replaced following code

(*dataset_p)[*term_count_p - 9] = ERL_DRV_ATOM;
(*dataset_p)[*term_count_p - 8] = drv->atom_error;
(*dataset_p)[*term_count_p - 7] = ERL_DRV_INT;
(*dataset_p)[*term_count_p - 6] = error_code;
(*dataset_p)[*term_count_p - 5] = ERL_DRV_STRING;
(*dataset_p)[*term_count_p - 4] = (ErlDrvTermData) error;
(*dataset_p)[*term_count_p - 3] = strlen(error);
(*dataset_p)[*term_count_p - 2] = ERL_DRV_TUPLE;
(*dataset_p)[*term_count_p - 1] = 3;

(where dataset_p has type ErlDrvTermData** and term_count_p is an int*) with

append_to_dataset(9, *dataset_p, *term_count_p,
  ERL_DRV_ATOM, drv->atom_error,
  ERL_DRV_INT, error_code,
  ERL_DRV_STRING, (ErlDrvTermData) error, strlen(error),
  ERL_DRV_TUPLE, 3);

where append_to_dataset is defined as

void append_to_dataset(int n, ErlDrvTermData* dataset, int term_count, ...) {
  int i;
  va_list new_terms;
  va_start(new_terms, term_count);

  for (i = -n; i < 0; i++) {
    dataset[term_count + i] = va_arg(new_terms, ErlDrvTermData);
  }
  va_end(new_terms);
}

(diff to see that's the entire difference). It looks to me like behavior should be exactly identical, but while tests pass on the original code, the new version fails with an error message:

HUGE size (47278999994405)
make: *** [test] Aborted

What am I getting wrong?


Solution

  • Depending on the types of the expressions on the right hand side of the assignments in the first code snippet, the two code snippets may not be equivalent.

    Variable arguments undergo default argument promotions but no other conversion (as opposed to calling a prototyped function, where arguments are converted if necessary if they’re assignment-compatible). For the second code to work they must be of type ErlDrvTermData which in turn must the same as its default-promoted type to fetch the corresponding arguments correctly via va_arg.

    In particular, every argument but the first two (which aren’t variable arguments) need an explicit cast to be converted correctly:

    append_to_dataset(9, *dataset_p, *term_count_p,
      (ErlDrvTermData)ERL_DRV_ATOM, (ErlDrvTermData)drv->atom_error,
      (ErlDrvTermData)ERL_DRV_INT, (ErlDrvTermData)error_code,
      (ErlDrvTermData)ERL_DRV_STRING,
      (ErlDrvTermData) error, (ErlDrvTermData)strlen(error),
      (ErlDrvTermData)ERL_DRV_TUPLE, 3);
    

    assuming the conversions are value-preserving. If ErlDrvTermData is, for example, typedefed to a short, it must be fetched as an int:

    dataset[term_count + i] = va_arg(new_terms, int);