检查const数组是否在编译时排序(C语言)

s8vozzvw  于 6个月前  发布在  其他
关注(0)|答案(2)|浏览(63)

在C语言中,如何在编译时检查常量数组是否排序(按升序)。
一个像这样的数组:const int a[] = {4,5,6,8};
如果上面的数组没有排序,编译应该失败并出错。
这可能吗

aurhwmvo

aurhwmvo1#

这在编译时很难做到,甚至用标准工具(预处理器等)都不可能做到。
也许一个更好的解决方案是,在数组创建后,尽快让代码检查它(你也可以像assert一样禁用它):

const int a[] = { 4, 5, 6, 8 };
#ifndef NDEBUG
for (int i = 1; i < sizeof(a) / sizeof(a[0]); i++) {
    if (a[i-1] > a[i]) {
        fprintf (stderr, "%s(%d) Check your arrays\n", __FILE__, __LINE__);
        exit(1);
    }
}
#endif

字符串

sg24os4d

sg24os4d2#

是的,有可能。
虽然有些人认为预处理器可以提供帮助,但它不是一个完整的解决方案,因为预处理器是一种受限的脚本语言,只能对文件中的文本进行操作,不能对编译结果中的值进行操作,并且预处理器不能像高级语言那样访问一般意义上的循环或递归。
但这并不能阻止我们在 * 编译时 * 检测数组是否排序,我们可以通过使用编译器优化器来计算列表是否排序并将该值放入编译后的二进制文件的.rodata部分来实现这一点。然后,我们可以解析编译后的目标文件,从中挖掘指示确定结果的数据,并在链接最终的二进制文件之前知道列表是否排序。是的,这很棘手,但它是可以做到!
对于这个答案,我做几个假设:
1.您希望在代码中有一个用于更改数组内容的单点控制
1.您在构建过程中使用了 make,并且可以修改Makefile
1.您可以访问Linux命令行工具的标准套件。
1.您使用的是 C++(稍后将详细介绍)
以下是概念验证:
main.cpp:

#include <stddef.h>
#include <stdint.h>
// isListSorted is evaluated at compile time to populate a const int
constexpr uint8_t isListSorted(const int * list, size_t sizeOfList)
{
    uint8_t retVal = 1;
    for(size_t i = 1; i < sizeOfList; i++) retVal &= list[i-1] <= list[i];
    return retVal;
}
// numList needs to be sorted to pass compilation, last two values are out of order and 
// will not compile
const int numList [] = {1, 2, 3, 5, 4};
const uint8_t isSorted = isListSorted(numList, sizeof(numList)/sizeof(const int));
int main()
{    
    return 0; 
}

字符串
生成文件:

CXX?=g++
EXE:=sorted-const-array
SOURCES:=$(wildcard *.cpp)
OBJECTS:=$(patsubst %.cpp, %.o, $(SOURCES))
.PHONY: all
all: $(EXE)
# Target for all cpp files
%.o: %.cpp
    $(CXX) -c $^ -o $@
# This dependency chain means that if main.cpp changes, then the sort check is recomputed 
# This target picks out the .rodata section of the object file
main.rodata: main.o
    objcopy -j.rodata -O binary $< $@ 
# This target picks out the is sorted determination in the .rodata section
main.bin: main.rodata 
    dd if=$< of=$@ bs=1 skip=`readelf -Ws main.o | grep -m 1 isSorted\
     | awk '{split($$0,a," ");print a[2]}' | xargs echo "ibase=16;" | bc` count=1
# This Phony Target produces no files, but returns 0 to make when the scan of
# main.o indicates arrays are not sorted in the else case. The is the mechanism that 
# guards against compiling unordered arrays
.PHONY: checkNumListSorted
checkNumListSorted: main.bin
    @if [ `xxd $< | awk '{split($$0,a," ");print a[2]}'` = "01" ]; then\
        echo "Arrays sorted";\
    else\
        echo "Arrays NOT sorted"; false;\
    fi
# Make the top level target dependent on a phony target that succeeds if and  
# only if numList is sorted. 
$(EXE): checkNumListSorted $(OBJECTS) 
    $(CXX) $(OBJECTS) $(LIB) -o $@
.PHONY: clean
clean:
    rm -f $(EXE) *.o *.rodata *.bin


运行会产生:

$ make 
g++ -c main.cpp -o main.o
objcopy -j.rodata -O binary main.o main.rodata 
dd if=main.rodata of=main.bin bs=1 skip=`readelf -Ws main.o | grep -m 1 isSorted\
 | awk '{split($0,a," ");print a[2]}' | xargs echo "ibase=16;" | bc` count=1
1+0 records in
1+0 records out
1 byte copied, 0.000301472 s, 3.3 kB/s
Arrays NOT sorted
make: *** [Makefile:24: checkNumListSorted] Error 1


修复顺序并重新运行构建过程将生成

$ make 
g++ -c main.cpp -o main.o
objcopy -j.rodata -O binary main.o main.rodata 
dd if=main.rodata of=main.bin bs=1 skip=`readelf -Ws main.o | grep -m 1 isSorted\
 | awk '{split($0,a," ");print a[2]}' | xargs echo "ibase=16;" | bc` count=1
1+0 records in
1+0 records out
1 byte copied, 0.000102414 s, 9.8 kB/s
Arrays sorted
g++  main.o  -o sorted-const-array


我之所以使用C++,是因为我利用constexpr作为编译器的一个提示,告诉编译器计算我们是否排序的函数是可以计算的。这使我们能够访问可以对任意大小的数据进行操作的循环,并允许我们在编译时进行计算。但是,如果你特别要求一个 C 实现,这就是欺骗。尽管constexpr已经存在于C++中如果你在2024年初使用的是绝对最新的编译器,你可以在C语言中使用这种方法。
对于路人来说,为什么要做这样的事情?对于涉及多个开发人员的大型项目,在很长一段时间内,您需要自动化的方法来检查代码所依赖的假设。这可以保护您免受奇怪的、难以重现的错误的影响。在 * 编译时 * 执行此操作可以确保您不会将此检查工作转移到用户身上。由于假设被破坏而导致的构建失败,是一个很好的自动检查。

相关问题