在C99和GCC中实现一个函数 <tgmath.h>。

huangapple go评论91阅读模式
英文:

implement a function in C99 <tgmath.h> and GCC

问题

I'm trying to follow this guide to implement the cbrt function in a type-generic way that works for float and double.

My C code:

  1. #include <math.h>
  2. /* based on https://web.archive.org/web/20131205042841/http://carolina.mff.cuni.cz/~trmac/blog/2005/the-ugliest-c-feature-tgmathh/ */
  3. #define __has_integer_type(x) ((__typeof__(x))1.25 == 1)
  4. #define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
  5. #define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
  6. #define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
  7. #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  8. double my_cbrt1(double x) { return cbrt(x); }
  9. double my_cbrt2(int x) { return cbrt(x); }
  10. float my_cbrt3(float x) { return cbrt(x); }

As indicated by https://godbolt.org/z/MW84rvca7 , GCC (gcc -std=c99) fails to compile it. I've tried several GCC versions ranging from 4.8 to 10.4. GCC 10.4 reports the following errors:

  1. C source #1x86-64 gcc 10.4 (Editor #1)Output of x86-64 gcc 10.4 (Compiler #1)
  2. <source>: In function 'my_cbrt1':
  3. <source>:9:37: error: variable or field '__result' declared void
  4. 9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  5. | ^~~~~~~~
  6. <source>:11:36: note: in expansion of macro 'cbrt'
  7. 11 | double my_cbrt1(double x) { return cbrt(x); }
  8. | ^~~~
  9. <source>: In function 'my_cbrt3':
  10. <source>:9:37: error: variable or field '__result' declared void
  11. 9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  12. | ^~~~~~~~
  13. <source>:13:34: note: in expansion of macro 'cbrt'
  14. 13 | float my_cbrt3(float x) { return cbrt(x); }
  15. | ^~~~

What am I doing wrong? Is the guide wrong, is my solution wrong, or does GCC implement the C99 standard incorrectly? How can it be fixed so that gcc -std=c99 compiles it?

Please note I'd like to fix the (void*) approach described in the guide above, and thus in this question I'm not interested in the following workarounds:

  • _Generic(...) with gcc -std=c11.
  • GCC __builtin_tgmath.
  • GCC __builtin_choose_expr.
  • GCC __builtin_classify_type. It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_classify_type.
  • GCC __builtin_types_compatible_p (see here). FYI It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_types_compatible_p.

FYI See https://stackoverflow.com/questions/2726712/how-is-tgmath-h-implemented for a similar question, about <tgmath.h>, but it doesn't answer my questions about this specific implementation and GCC.

https://stackoverflow.com/questions/2726712/how-is-tgmath-h-implemented

英文:

I'm trying to follow this guide to implement the cbrt function in a type-generic way that works for float and double.

My C code:

  1. #include &lt;math.h&gt;
  2. /* based on https://web.archive.org/web/20131205042841/http://carolina.mff.cuni.cz/~trmac/blog/2005/the-ugliest-c-feature-tgmathh/ */
  3. #define __has_integer_type(x) ((__typeof__(x))1.25 == 1)
  4. #define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
  5. #define __e2_old(x) (1 ? (int *)0 : (void *)(!__has_integer_type(x)))*/
  6. #define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
  7. #define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
  8. #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) &amp;&amp; !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  9. double my_cbrt1(double x) { return cbrt(x); }
  10. double my_cbrt2(int x) { return cbrt(x); }
  11. float my_cbrt3(float x) { return cbrt(x); }

As indicated by https://godbolt.org/z/MW84rvca7 , GCC (gcc -std=c99) fails to compile it. I've tried several GCC versions ranging from 4.8 to 10.4. GCC 10.4 reports the following errors:

  1. C source #1x86-64 gcc 10.4 (Editor #1)Output of x86-64 gcc 10.4 (Compiler #1)
  2. &lt;source&gt;: In function &#39;my_cbrt1&#39;:
  3. &lt;source&gt;:9:37: error: variable or field &#39;__result&#39; declared void
  4. 9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) &amp;&amp; !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  5. | ^~~~~~~~
  6. &lt;source&gt;:11:36: note: in expansion of macro &#39;cbrt&#39;
  7. 11 | double my_cbrt1(double x) { return cbrt(x); }
  8. | ^~~~
  9. &lt;source&gt;: In function &#39;my_cbrt3&#39;:
  10. &lt;source&gt;:9:37: error: variable or field &#39;__result&#39; declared void
  11. 9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) &amp;&amp; !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  12. | ^~~~~~~~
  13. &lt;source&gt;:13:34: note: in expansion of macro &#39;cbrt&#39;
  14. 13 | float my_cbrt3(float x) { return cbrt(x); }
  15. | ^~~~

What am I doing wrong? Is the guide wrong, is my solution wrong, or does GCC implement the C99 standard incorrectly? How can it be fixed so that gcc -std=c99 compiles it?

Please note I'd like to fix the (void*) approach described in the guide above, and thus in this question I'm not interested in the following workarounds:

  • _Generic(...) with gcc -std=c11.
  • GCC __builtin_tgmath.
  • GCC __builtin_choose_expr.
  • GCC __builtin_classify_type. It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_classify_type.
  • GCC __builtin_types_compatible_p (see here). FYI It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_types_compatible_p.

FYI See https://stackoverflow.com/questions/2726712/how-is-tgmath-h-implemented for a similar question, about &lt;tgmath.h&gt;, but it doesn't answer my questions about this specific implementation and GCC.

https://stackoverflow.com/questions/2726712/how-is-tgmath-h-implemented

答案1

得分: 0

失败的原因是GCC和Clang不将包含浮点值的表达式视为整数常量表达式,如下所示:

  1. prog.c: In function 'x':
  2. prog.c:1:22: error: first argument to '__builtin_choose_expr' not a constant
  3. int x(void) { return __builtin_choose_expr(0.25 == 0.25, 5, 6); }
  1. prog.c: In function 'x':
  2. prog.c:1:22: error: first argument to '__builtin_choose_expr' not a constant
  3. int x(void) { return __builtin_choose_expr((int)(double)1, 5, 6); }

我不太了解C99,无法确定GCC的正确性。

要修复它,__has_integer_type(x) 需要更改为不包含浮点数的内容,甚至不包含临时浮点数。不幸的是,似乎没有办法做到这一点,除非使用 __builtin_classify_type__builtin_types_compatible_p

然而,对于GCC的 __builtin_classify_type,这很容易实现(也可以参见 https://godbolt.org/z/43aYer9hE):

  1. #include <math.h>
  2. #define __has_integer_type(x) (__builtin_classify_type(x) != 8)
  3. #define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
  4. #define __e2_old(x) (1 ? (int *)0 : (void *)(!__has_integer_type(x)))*/
  5. #define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
  6. #define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
  7. #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  8. double my_cbrt1(double x) { return cbrt(x); }
  9. double my_cbrt2(int x) { return cbrt(x); }
  10. float my_cbrt3(float x) { return cbrt(x); }
英文:

The reason for the failure is that GCC and Clang don't treat expressons containing floating point values as an integer constant expression, as demonstrated here:

  1. prog.c: In function &#39;x&#39;:
  2. prog.c:1:22: error: first argument to &#39;__builtin_choose_expr&#39; not a constant
  3. int x(void) { return __builtin_choose_expr(0.25 == 0.25, 5, 6); }
  1. prog.c: In function &#39;x&#39;:
  2. prog.c:1:22: error: first argument to &#39;__builtin_choose_expr&#39; not a constant
  3. int x(void) { return __builtin_choose_expr((int)(double)1, 5, 6); }

I don't know C99 well enough to decide whether GCC is correct here.

To fix it, the __has_integer_type(x) needs to be changed to something which doesn't contain floating-point numbers, not even as temporaries. Unfortunately this doesn't seem to be possible without __builtin_classify_type and __builtin_types_compatible_p.

However, with GCC __builtin_classify_type, it is easy (see also https://godbolt.org/z/43aYer9hE):

  1. #include &lt;math.h&gt;
  2. #define __has_integer_type(x) (__builtin_classify_type(x) != 8)
  3. #define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
  4. #define __e2_old(x) (1 ? (int *)0 : (void *)(!__has_integer_type(x)))*/
  5. #define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
  6. #define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
  7. #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) &amp;&amp; !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
  8. double my_cbrt1(double x) { return cbrt(x); }
  9. double my_cbrt2(int x) { return cbrt(x); }
  10. float my_cbrt3(float x) { return cbrt(x); }

huangapple
  • 本文由 发表于 2023年6月12日 20:09:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76456539.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定