Skip to main content
added test program
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21

This usually fails to compile when the caller fails to provide a string literal argument. As with SVLEN_SAFE beforeIn fact, this version willwhen I used GCC 14.2 to compile SV_SAFE("this" - "that"), but unlike SVLEN_SAFEtest cases with this macro, all cases that attempted to call SV_SAFE will (probably) generatewith anything other than a warning herestring literal argument failed to compile. One test case, (or an error ifSV_SAFE("abc" - "def") failed to compile in this test, but some compilers might compile it with a warning; use -Werror or similar is enabled) since this would attempt to assign an integer value (the value of "this" - "that") to a pointer variable (the s field of a str_view struct)force compiler errors here.

Test Program

Here is a test program to verify that SV_SAFE works and that it fails to compile for unwanted inputs. Failing tests are commented out. It may be possible to break this macro, but it appears to guard against non-string-literal inputs reasonably well. Failing tests are commented out.

I have also included an implementation of make_sv which I alluded to earlier in this answer. make_sv is a function which takes a pointer argument instead of a string literal. This has several advantages, one of which is much better error messages. Since it takes a pointer instead of a string literal you could use it with dynamically created strings or with arrays containing strings.

The compiler errors shown in comments to the right of test cases were generated by GCC 14.2 with the invocation gcc string_view_tests.c. This compiled in the default gnu17 mode, with no warnings enabled.

#include <stdio.h>
#include <string.h>

typedef struct {
  const char *s;
  size_t len;
} str_view;

#define SV_SAFE(str) ((str_view){(str ""), sizeof(str) - 1U})

// Do we really need an `SV` macro at all?
// `make_sv` returns an empty `str_view` if it receives a null pointer argument.
str_view make_sv(const char *s) {
  if (s) {
  return (str_view){ .s = s,
                     .len = strlen(s)};
  }
  return (str_view){ .s = "",
                     .len = 0};
}

int main(void) {
  const char *s = "hello";
  char word[] = "hello";
  double a = 0.0f;
  double *d = &a;

  puts("testing SV_SAFE macro:");
  str_view this = SV_SAFE("this macro");
  printf("%s: .len = %zu\n", this.s, this.len);
  
  // Some tests that should fail:
  // str_view test = SV_SAFE(NULL);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(word);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(d);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(s);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(a);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(x);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(-);  //--> error: wrong type argument to unary minus
  // str_view test = SV_SAFE();  //--> error: expected expression before ')' token

  // Some implementations might compile this test with a warning.
  // Use `-Werror` or similar to force an error.
  // str_view test = SV_SAFE("abc" - "def");  //--> error: initialization of 'const char *' from 'long long int' makes pointer from integer without a cast [-Wint-conversion]
  
  puts("\ntesting make_sv function:");
  str_view that = make_sv("that function");
  printf("%s: .len = %zu\n", that.s, that.len);

  puts("\ntesting make_sv(NULL):");
  str_view null_test = make_sv(NULL);
  printf("%s: .len = %zu\n", null_test.s, null_test.len);

  puts("\ntesting make_sv(word):");
  str_view word_test = make_sv(word);
  printf("%s: .len = %zu\n", word_test.s, word_test.len);

  puts("\ntesting make_sv(s):");
  str_view s_test = make_sv(s);
  printf("%s: .len = %zu\n", s_test.s, s_test.len);

  // Some tests that should fail:
  // str_view test = make_sv(NULL);  // no longer failing
  // str_view test = make_sv(word);  // no longer failing
  // str_view test = make_sv(d);  //--> error: passing argument 1 of 'make_sv' from incompatible pointer type [-Wincompatible-pointer-types]
  // str_view test = make_sv(s);  // no longer failing
  // str_view test = make_sv(a);  //--> error: incompatible type for argument 1 of 'make_sv'
  // str_view test = make_sv(x);  //--> error: 'x' undeclared (first use in this function)
  // str_view test = make_sv(-);  //--> error: expected expression before ')' token
  // str_view test = make_sv();  //--> error: too few arguments to function 'make_sv'
  // str_view test = make_sv("abc" - "def");  //--> error: passing argument 1 of 'make_sv' makes pointer from integer without a cast [-Wint-conversion]
}
```

This usually fails to compile when the caller fails to provide a string literal argument. As with SVLEN_SAFE before, this version will compile SV_SAFE("this" - "that"), but unlike SVLEN_SAFE, SV_SAFE will (probably) generate a warning here (or an error if -Werror or similar is enabled) since this would attempt to assign an integer value (the value of "this" - "that") to a pointer variable (the s field of a str_view struct).

This usually fails to compile when the caller fails to provide a string literal argument. In fact, when I used GCC 14.2 to compile test cases with this macro, all cases that attempted to call SV_SAFE with anything other than a string literal argument failed to compile. One test case, SV_SAFE("abc" - "def") failed to compile in this test, but some compilers might compile it with a warning; use -Werror or similar to force compiler errors here.

Test Program

Here is a test program to verify that SV_SAFE works and that it fails to compile for unwanted inputs. Failing tests are commented out. It may be possible to break this macro, but it appears to guard against non-string-literal inputs reasonably well. Failing tests are commented out.

I have also included an implementation of make_sv which I alluded to earlier in this answer. make_sv is a function which takes a pointer argument instead of a string literal. This has several advantages, one of which is much better error messages. Since it takes a pointer instead of a string literal you could use it with dynamically created strings or with arrays containing strings.

The compiler errors shown in comments to the right of test cases were generated by GCC 14.2 with the invocation gcc string_view_tests.c. This compiled in the default gnu17 mode, with no warnings enabled.

#include <stdio.h>
#include <string.h>

typedef struct {
  const char *s;
  size_t len;
} str_view;

#define SV_SAFE(str) ((str_view){(str ""), sizeof(str) - 1U})

// Do we really need an `SV` macro at all?
// `make_sv` returns an empty `str_view` if it receives a null pointer argument.
str_view make_sv(const char *s) {
  if (s) {
  return (str_view){ .s = s,
                     .len = strlen(s)};
  }
  return (str_view){ .s = "",
                     .len = 0};
}

int main(void) {
  const char *s = "hello";
  char word[] = "hello";
  double a = 0.0f;
  double *d = &a;

  puts("testing SV_SAFE macro:");
  str_view this = SV_SAFE("this macro");
  printf("%s: .len = %zu\n", this.s, this.len);
  
  // Some tests that should fail:
  // str_view test = SV_SAFE(NULL);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(word);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(d);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(s);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(a);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(x);  //--> error: expected ')' before string constant
  // str_view test = SV_SAFE(-);  //--> error: wrong type argument to unary minus
  // str_view test = SV_SAFE();  //--> error: expected expression before ')' token

  // Some implementations might compile this test with a warning.
  // Use `-Werror` or similar to force an error.
  // str_view test = SV_SAFE("abc" - "def");  //--> error: initialization of 'const char *' from 'long long int' makes pointer from integer without a cast [-Wint-conversion]
  
  puts("\ntesting make_sv function:");
  str_view that = make_sv("that function");
  printf("%s: .len = %zu\n", that.s, that.len);

  puts("\ntesting make_sv(NULL):");
  str_view null_test = make_sv(NULL);
  printf("%s: .len = %zu\n", null_test.s, null_test.len);

  puts("\ntesting make_sv(word):");
  str_view word_test = make_sv(word);
  printf("%s: .len = %zu\n", word_test.s, word_test.len);

  puts("\ntesting make_sv(s):");
  str_view s_test = make_sv(s);
  printf("%s: .len = %zu\n", s_test.s, s_test.len);

  // Some tests that should fail:
  // str_view test = make_sv(NULL);  // no longer failing
  // str_view test = make_sv(word);  // no longer failing
  // str_view test = make_sv(d);  //--> error: passing argument 1 of 'make_sv' from incompatible pointer type [-Wincompatible-pointer-types]
  // str_view test = make_sv(s);  // no longer failing
  // str_view test = make_sv(a);  //--> error: incompatible type for argument 1 of 'make_sv'
  // str_view test = make_sv(x);  //--> error: 'x' undeclared (first use in this function)
  // str_view test = make_sv(-);  //--> error: expected expression before ')' token
  // str_view test = make_sv();  //--> error: too few arguments to function 'make_sv'
  // str_view test = make_sv("abc" - "def");  //--> error: passing argument 1 of 'make_sv' makes pointer from integer without a cast [-Wint-conversion]
}
```
deleted 251 characters in body
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21

To be clear: the OP macro, and the related macros in this answer, will only work with arguments that are string literals; OP has specified as much in the question and included code. It would be quite easy to get tripped up using these definitions.

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

To be clear: the OP macro, and the related macros in this answer, will only work with arguments that are string literals; OP has specified as much in the question and included code. It would be quite easy to get tripped up using these definitions.

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

added 656 characters in body
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21

To be clear: the OP macro, and the related macros in this answer, will only work with arguments that are string literals; OP has specified as much in the question and included code. It would be quite easy to get tripped up using these definitions.

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

Also note that rare unicorn platforms may exist where size_t is narrower than int. To guard against potential problems which may arise when a caller expects an unsigned result, unsigned math can be ensured by subtracting an unsigned integer constant from the result of sizeof. (@Davislor)

#define SV_SAFE(str) ((str_view){(str ""), sizeof(str) - 11U})
#define SV_SAFE_ZERO(str) ((str_view){(str ""), sizeof(str "") - 11U})

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

#define SV_SAFE(str) ((str_view){(str ""), sizeof(str) - 1})
#define SV_SAFE_ZERO(str) ((str_view){(str ""), sizeof(str "") - 1})

To be clear: the OP macro, and the related macros in this answer, will only work with arguments that are string literals; OP has specified as much in the question and included code. It would be quite easy to get tripped up using these definitions.

It would be worth the OP's time to review whether SV needs to be a macro at all. Maybe it would be better to use a make_sv function that returns a str_view struct, allowing the caller to determine whether a string is backed by string literal storage or some other storage for which the caller is responsible. I would probably favor that direction.

Also note that rare unicorn platforms may exist where size_t is narrower than int. To guard against potential problems which may arise when a caller expects an unsigned result, unsigned math can be ensured by subtracting an unsigned integer constant from the result of sizeof. (@Davislor)

#define SV_SAFE(str) ((str_view){(str ""), sizeof(str) - 1U})
#define SV_SAFE_ZERO(str) ((str_view){(str ""), sizeof(str "") - 1U})
added 528 characters in body
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21
Loading
added 97 characters in body
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21
Loading
Source Link
ad absurdum
  • 894
  • 1
  • 10
  • 21
Loading