use qsort_r() when it is available - relates to #622

Thu, 18 Dec 2025 12:11:30 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 18 Dec 2025 12:11:30 +0100
changeset 1620
bf5d647f939d
parent 1619
0db02ab1457c
child 1621
c52a4c67e29e

use qsort_r() when it is available - relates to #622

configure file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
make/test_memrchr.c file | annotate | diff | comparison | revisions
make/test_qsort_r.c file | annotate | diff | comparison | revisions
src/array_list.c file | annotate | diff | comparison | revisions
src/cx/array_list.h file | annotate | diff | comparison | revisions
src/cx/list.h file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
--- a/configure	Wed Dec 17 20:13:08 2025 +0100
+++ b/configure	Thu Dec 18 12:11:30 2025 +0100
@@ -156,9 +156,10 @@
   --enable-api-docs       run Doxygen during build
   --enable-coverage       test coverage with gcov
   --enable-asan           address sanitizer
-  --disable-memrchr
   --disable-cxx-tests     the check-cxx makefile target
   --disable-szmul-builtin use custom implementation, instead
+  --disable-memrchr
+  --disable-qsort_r
 
 __EOF__
     abort_configure
@@ -278,9 +279,10 @@
 fi
 
 # features
-FEATURE_MEMRCHR=auto
 FEATURE_CXX_TESTS=auto
 FEATURE_SZMUL_BUILTIN=auto
+FEATURE_MEMRCHR=auto
+FEATURE_QSORT_R=auto
 
 #
 # parse arguments
@@ -304,12 +306,14 @@
         "--disable-coverage") unset FEATURE_COVERAGE ;;
         "--enable-asan") FEATURE_ASAN=on ;;
         "--disable-asan") unset FEATURE_ASAN ;;
-        "--enable-memrchr") FEATURE_MEMRCHR=on ;;
-        "--disable-memrchr") unset FEATURE_MEMRCHR ;;
         "--enable-cxx-tests") FEATURE_CXX_TESTS=on ;;
         "--disable-cxx-tests") unset FEATURE_CXX_TESTS ;;
         "--enable-szmul-builtin") FEATURE_SZMUL_BUILTIN=on ;;
         "--disable-szmul-builtin") unset FEATURE_SZMUL_BUILTIN ;;
+        "--enable-memrchr") FEATURE_MEMRCHR=on ;;
+        "--disable-memrchr") unset FEATURE_MEMRCHR ;;
+        "--enable-qsort_r") FEATURE_QSORT_R=on ;;
+        "--disable-qsort_r") unset FEATURE_QSORT_R ;;
         "-"*) echo "unknown option: $ARG"; abort_configure ;;
     esac
 done
@@ -535,7 +539,7 @@
     # dependency memrchr
     while true
     do
-        if $TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null -D_GNU_SOURCE make/test_memrchr.c > /dev/null 2>&1 ; then
+        if $TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null make/test_memrchr.c > /dev/null 2>&1 ; then
             :
         else
             break
@@ -590,6 +594,27 @@
     dep_checked_no_coverage=1
     return 0
 }
+dependency_error_qsort_r()
+{
+    print_check_msg "$dep_checked_qsort_r" "checking for qsort_r... "
+    # dependency qsort_r
+    while true
+    do
+        if $TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null make/test_qsort_r.c > /dev/null 2>&1 ; then
+            :
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DWITH_QSORT_R"
+        print_check_msg "$dep_checked_qsort_r" "yes\n"
+        dep_checked_qsort_r=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_qsort_r" "no\n"
+    dep_checked_qsort_r=1
+    return 0
+}
 
 # start collecting dependency information
 echo > "$TEMP_DIR/flags.mk"
@@ -801,26 +826,6 @@
 else
     :
 fi
-if [ -n "$FEATURE_MEMRCHR" ]; then
-    # check dependency
-    if dependency_error_memrchr ; then
-        # "auto" features can fail and are just disabled in this case
-        if [ "$FEATURE_MEMRCHR" = "auto" ]; then
-            DISABLE_FEATURE_MEMRCHR=1
-        else
-            DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED memrchr "
-            ERROR=1
-        fi
-    fi
-    if [ -n "$DISABLE_FEATURE_MEMRCHR" ]; then
-        unset FEATURE_MEMRCHR
-    fi
-fi
-if [ -n "$FEATURE_MEMRCHR" ]; then
-    :
-else
-    :
-fi
 if [ -n "$FEATURE_CXX_TESTS" ]; then
     # check dependency
     if dependency_error_cxx ; then
@@ -867,6 +872,46 @@
     TEMP_CFLAGS="$TEMP_CFLAGS -DCX_NO_SZMUL_BUILTIN"
     TEMP_CXXFLAGS="$TEMP_CXXFLAGS -DCX_NO_SZMUL_BUILTIN"
 fi
+if [ -n "$FEATURE_MEMRCHR" ]; then
+    # check dependency
+    if dependency_error_memrchr ; then
+        # "auto" features can fail and are just disabled in this case
+        if [ "$FEATURE_MEMRCHR" = "auto" ]; then
+            DISABLE_FEATURE_MEMRCHR=1
+        else
+            DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED memrchr "
+            ERROR=1
+        fi
+    fi
+    if [ -n "$DISABLE_FEATURE_MEMRCHR" ]; then
+        unset FEATURE_MEMRCHR
+    fi
+fi
+if [ -n "$FEATURE_MEMRCHR" ]; then
+    :
+else
+    :
+fi
+if [ -n "$FEATURE_QSORT_R" ]; then
+    # check dependency
+    if dependency_error_qsort_r ; then
+        # "auto" features can fail and are just disabled in this case
+        if [ "$FEATURE_QSORT_R" = "auto" ]; then
+            DISABLE_FEATURE_QSORT_R=1
+        else
+            DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED qsort_r "
+            ERROR=1
+        fi
+    fi
+    if [ -n "$DISABLE_FEATURE_QSORT_R" ]; then
+        unset FEATURE_QSORT_R
+    fi
+fi
+if [ -n "$FEATURE_QSORT_R" ]; then
+    :
+else
+    :
+fi
 
 
 if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
@@ -938,12 +983,6 @@
 else
     echo 'off'
 fi
-printf '  %-16s' 'memrchr:'
-if [ -n "$FEATURE_MEMRCHR" ]; then
-    echo 'on'
-else
-    echo 'off'
-fi
 printf '  %-16s' 'cxx-tests:'
 if [ -n "$FEATURE_CXX_TESTS" ]; then
     echo 'on'
@@ -956,6 +995,18 @@
 else
     echo 'off'
 fi
+printf '  %-16s' 'memrchr:'
+if [ -n "$FEATURE_MEMRCHR" ]; then
+    echo 'on'
+else
+    echo 'off'
+fi
+printf '  %-16s' 'qsort_r:'
+if [ -n "$FEATURE_QSORT_R" ]; then
+    echo 'on'
+else
+    echo 'off'
+fi
 echo
 
 # generate the config.mk file
--- a/make/project.xml	Wed Dec 17 20:13:08 2025 +0100
+++ b/make/project.xml	Thu Dec 18 12:11:30 2025 +0100
@@ -99,10 +99,15 @@
     </dependency>
 
     <dependency name="memrchr">
-        <test>$TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null -D_GNU_SOURCE make/test_memrchr.c</test>
+        <test>$TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null make/test_memrchr.c</test>
         <cflags>-DWITH_MEMRCHR</cflags>
     </dependency>
 
+    <dependency name="qsort_r">
+        <test>$TOOLCHAIN_CC $CFLAGS $LDFLAGS -o /dev/null make/test_qsort_r.c</test>
+        <cflags>-DWITH_QSORT_R</cflags>
+    </dependency>
+
     <target>
         <feature name="api-docs">
             <dependencies>doxygen</dependencies>
@@ -120,9 +125,6 @@
             <desc>address sanitizer</desc>
             <dependencies>asan</dependencies>
         </feature>
-        <feature name="memrchr" default="true">
-            <dependencies>memrchr</dependencies>
-        </feature>
         <feature name="cxx-tests" default="true">
             <dependencies>cxx</dependencies>
             <desc>the check-cxx makefile target</desc>
@@ -138,5 +140,11 @@
                 <define name="CX_NO_SZMUL_BUILTIN"/>
             </disabled>
         </feature>
+        <feature name="memrchr" default="true">
+            <dependencies>memrchr</dependencies>
+        </feature>
+        <feature name="qsort_r" default="true">
+            <dependencies>qsort_r</dependencies>
+        </feature>
     </target>
 </project>
--- a/make/test_memrchr.c	Wed Dec 17 20:13:08 2025 +0100
+++ b/make/test_memrchr.c	Thu Dec 18 12:11:30 2025 +0100
@@ -1,3 +1,4 @@
+#define _GNU_SOURCE
 #include <string.h>
 
 int main() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/test_qsort_r.c	Thu Dec 18 12:11:30 2025 +0100
@@ -0,0 +1,14 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+
+static int f(const void *a, const void *b, void *c) {
+    return 0; // not important what it does for this test
+}
+
+int main() {
+    int x[4] = {3, 1, 4, 0};
+    int z = 47;
+    void *c = &z;
+    qsort_r(x, 4, sizeof(int), f, c);
+    return 0;
+}
--- a/src/array_list.c	Wed Dec 17 20:13:08 2025 +0100
+++ b/src/array_list.c	Thu Dec 18 12:11:30 2025 +0100
@@ -26,6 +26,10 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifdef WITH_MEMRCHR
+#define _GNU_SOURCE
+#endif
+
 #include "cx/array_list.h"
 #include "cx/compare.h"
 #include <assert.h>
@@ -349,6 +353,25 @@
         n, allow_duplicates, cx_acmp_wrap, &wrapper);
 }
 
+#ifndef WITH_QSORT_R
+static thread_local cx_compare_func2 cx_array_fn_for_qsort;
+static thread_local void *cx_array_context_for_qsort;
+static int cx_array_qsort_wrapper(const void *l, const void *r) {
+    return cx_array_fn_for_qsort(l, r, cx_array_context_for_qsort);
+}
+#endif
+
+void cx_array_qsort_c(void *array, size_t nmemb, size_t size,
+        cx_compare_func2 fn, void *context) {
+#ifdef WITH_QSORT_R
+    qsort_r(array, nmemb, size, fn, context);
+#else
+    cx_array_fn_for_qsort = fn;
+    cx_array_context_for_qsort = context;
+    qsort(array, nmemb, size, cx_array_qsort_wrapper);
+#endif
+}
+
 CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) {
     return cxIterator(array->data, elem_size, array->size);
 }
@@ -849,19 +872,12 @@
     return list->collection.size;
 }
 
-// TODO: remove this hack once we have a solution for qsort() / qsort_s()
-static _Thread_local CxList *cx_hack_for_qsort_list;
-static int cx_hack_cmp_for_qsort(const void *l, const void *r) {
-    return cx_list_compare_wrapper(l, r, cx_hack_for_qsort_list);
-}
-
 static void cx_arl_sort(struct cx_list_s *list) {
-    // TODO: think about if we can somehow use qsort()_s
-    cx_hack_for_qsort_list = list;
-    qsort(((cx_array_list *) list)->data,
+    cx_array_qsort_c(((cx_array_list *) list)->data,
           list->collection.size,
           list->collection.elem_size,
-          cx_hack_cmp_for_qsort
+          cx_list_compare_wrapper,
+          list
     );
 }
 
--- a/src/cx/array_list.h	Wed Dec 17 20:13:08 2025 +0100
+++ b/src/cx/array_list.h	Thu Dec 18 12:11:30 2025 +0100
@@ -546,6 +546,20 @@
         cx_array_insert_unique_array_a(cxDefaultAllocator, array, cmp_func, sorted_data, n)
 
 /**
+ * An alternative to qsort_r() when that is not available on your platform.
+ *
+ * If it is available, qsort_r() is used directly.
+ *
+ * @param array the array that shall be sorted
+ * @param nmemb the number of elements in the array
+ * @param size the size of one element
+ * @param fn the compare function
+ * @param context the context for the compare function
+ */
+CX_EXPORT void cx_array_qsort_c(void *array, size_t nmemb, size_t size,
+        cx_compare_func2 fn, void *context);
+
+/**
  * Creates an iterator over the elements of an array.
  *
  * Internal function - do not use.
--- a/src/cx/list.h	Wed Dec 17 20:13:08 2025 +0100
+++ b/src/cx/list.h	Thu Dec 18 12:11:30 2025 +0100
@@ -287,11 +287,8 @@
  * The purpose of this function is to be called in the initialization code
  * of your list to set certain members correctly.
  *
- * This is particularly important when you want your list to support
- * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list
- * class accordingly and make sure that you can implement your list as if
- * it was only storing objects, and the wrapper will automatically enable
- * the feature of storing pointers.
+ * This is particularly useful when you want your list to support
+ * #CX_STORE_POINTERS as @p elem_size.
  *
  * @par Example
  *
--- a/src/list.c	Wed Dec 17 20:13:08 2025 +0100
+++ b/src/list.c	Thu Dec 18 12:11:30 2025 +0100
@@ -31,6 +31,12 @@
 #include <string.h>
 #include <assert.h>
 
+// we don't want to include the full array_list.h.
+// therefore, we only forward declare the one function we want to use
+CX_EXPORT void cx_array_qsort_c(void *array, size_t nmemb, size_t size,
+        cx_compare_func2 fn, void *context);
+
+
 int cx_list_compare_wrapper(const void *l, const void *r, void *c) {
     CxList *list = c;
     const void *left;
@@ -283,12 +289,6 @@
     return cx_list_default_insert_sorted_impl(list, sorted_data, n, false);
 }
 
-// TODO: remove this hack once we have a solution for qsort() / qsort_s()
-static _Thread_local CxList *cx_hack_for_qsort_list;
-static int cx_hack_cmp_for_qsort(const void *l, const void *r) {
-    return cx_list_compare_wrapper(l, r, cx_hack_for_qsort_list);
-}
-
 void cx_list_default_sort(struct cx_list_s *list) {
     size_t elem_size = list->collection.elem_size;
     size_t list_size = list->collection.size;
@@ -304,9 +304,7 @@
     }
 
     // qsort
-    // TODO: qsort_s() is not as nice as we thought
-    cx_hack_for_qsort_list = list;
-    qsort(tmp, list_size, elem_size, cx_hack_cmp_for_qsort);
+    cx_array_qsort_c(tmp, list_size, elem_size, cx_list_compare_wrapper, list);
 
     // copy elements back
     loc = tmp;
--- a/src/string.c	Wed Dec 17 20:13:08 2025 +0100
+++ b/src/string.c	Thu Dec 18 12:11:30 2025 +0100
@@ -26,8 +26,9 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-// for memrchr in glibc
+#ifdef WITH_MEMRCHR
 #define _GNU_SOURCE
+#endif
 
 #include "cx/string.h"
 

mercurial