如何确定指针是否等于数组的元素?

时间:2016-02-02 21:06:08

标签: c arrays pointers language-lawyer

我按照预期code in Code Reveiw “正常工作”,但可能UB

代码有一个名为char的大小相同的GP2_format[]数组。要检测指针format是否具有与其中一个元素GP2_format[][0]的地址相同的值,如果指针是>=最小元素并且<=,则下面的代码将进行简单测试} 最伟大的。由于元素大小为1,因此无需进一步检查。

const char GP2_format[GP2_format_N + 1][1];
const char *format = ...;

if (format >= GP2_format[0] && format <= GP2_format[GP2_format_N]) Inside()
else Outside();

C11§6.5.8/ 5当比较数组外部的指针时,关系运算符< > <= >=似乎将此定义为可怕的未定义行为

  

当比较两个指针时,结果取决于指向的对象的地址空间中的相对位置。如果两个指向对象类型的指针都指向   相同的对象,...相同的数组对象,他们比较相等。 ...(相同的对象OK)....(相同的联合OK)....(相同的数组OK)...在所有其他情况下,行为未定义

  

Q1代码的指针是否在GP2_get_type() UB中进行比较?   Q2如果是这样,那么一个定义明确的备选方案,搜索O(1)到可疑的GP2_get_type()

较慢的解决方案
代码可以针对每个format依次测试GP2_format[]或将值转换为intptr_t,排序一次并执行O(ln2(n))搜索。

类似
...if a pointer is part of a set,但这个“集合”不是随机的,而是一个数组 intptr_t approach - 也许是UB。

#include <stdio.h>

typedef enum {
  GP2_set_precision,
  GP2_set_w,
  GP2_setios_flags_,
  GP2_string_,
  GP2_unknown_,
  GP2_format_N
} GP2_type;

const char GP2_format[GP2_format_N + 1][1];

static int GP2_get_type(const char *format) {
  // candidate UB with pointer compare
  if (format >= GP2_format[0] && format <= GP2_format[GP2_format_N]) {
    return (int) (format - GP2_format[0]);
  }
  return GP2_format_N;
}

int main(void) {
  printf("%d\n", GP2_get_type(GP2_format[1]));
  printf("%d\n", GP2_get_type("Hello World"));  // potential UB
  return 0;
}

输出(按预期,但可能是UB)

1
5

2 个答案:

答案 0 :(得分:2)

如果您想遵守C标准,那么您的选择是:

  • 针对目标范围内的每个指针执行单独的==!=测试
    • 如果它是一个非常大的集合,您可以使用哈希表或搜索树或其他东西加快速度
  • 重新设计您的代码,不需要此检查。

“可能有用”的方法是将所有值转换为uintptr_t,然后进行关系比较。如果系统具有绝对排序的内存模型,则应定义uintptr_t并保留该排序;如果它没有这样的模型,那么关系比较的想法永远不会有效。

答案 1 :(得分:2)

这不是对所述问题的回答,而是对潜在问题的回答。

除非我弄错了,否则可以通过将GP_format作为字符串来避免整个问题。这样,问题简化为检查指针是否指向已知字符串,而不是UB。 (如果是,那么使用strchr()来查找字符并在字符串中计算其索引将是UB,这将是完全愚蠢的。在我看来,这将是标准中的一个严重错误。然后,我不是语言律师,只是一个程序员试图编写健壮,可移植的C.幸运的是,该标准声明它是为帮助像我这样的人而写的,而不是那些想要避免做艰苦工作的编译器编写者只要标准中的技术性允许它们就产生垃圾。)

以下是我想到的方法的完整示例。这也与clang-3.5一起编译,因为我目前使用的机器上的最新GCC是版本4.8.4,没有_Generic()支持。如果您使用不同版本的clang或gcc,请相应地更改Makefile中的第一行,或者例如make CC=gcc

首先,Makefile

CC  := clang-3.5
CFLAGS  := -Wall -Wextra -std=c11 -O2
LD      := $(CC)
LDFLAGS :=
PROGS   := example

.PHONY: all clean

all: clean $(PROGS)

clean:
    rm -f *.o $(PROGS)

%.o: %.c
    $(CC) $(CFLAGS) -c $^

example: out.o main.o
    $(LD) $^ $(LDFLAGS) -o $@

接下来,out.h

#ifndef   OUT_H
#define   OUT_H 1
#include <stdio.h>

typedef enum {
    out_char,
    out_int,
    out_double,
    out_FILE,
    out_set_fixed,
    out_set_width,
    out_set_decimals,
    out_count
} out_type;

extern const char out_formats[out_count + 1];

extern int outf(FILE *, ...);

#define out(x...) outf(stdout, x)
#define err(x...) outf(stderr, x)

#define OUT(x) _Generic( (x),           \
    FILE *: out_formats + out_FILE,     \
    double: out_formats + out_double,   \
    int:    out_formats + out_int,      \
    char:   out_formats + out_char      ), (x)

#define OUT_END         ((const char *)0)
#define OUT_EOL         "\n", ((const char *)0)
#define OUT_fixed(x)    (out_formats + out_set_fixed), ((int)(x))
#define OUT_width(x)    (out_formats + out_set_width), ((int)(x))
#define OUT_decimals(x) (out_formats + out_set_decimals), ((int)(x))

#endif /* OUT_H */

请注意,上面的OUT()宏扩展为由逗号分隔的两个子表达式。第一个子表达式使用_Generic()根据宏参数的类型在out_formats内发出指针。第二个子表达式是宏参数本身。

使outf()函数的第一个参数是固定的(要使用的初始流)简化了函数实现。

接下来,out.c

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include "out.h"

/* out_formats is a string consisting of ASCII NULs,
 * i.e. an array of zero chars.
 * We only check if a char pointer points to within out_formats,
 * if it points to a zero char; otherwise, it's just a normal
 * string we print as-is.
*/
const char out_formats[out_count + 1] = { 0 };

int outf(FILE *out, ...)
{
    va_list args;

    int     fixed = 0;
    int     width = -1;
    int     decimals = -1;

    if (!out)
        return EINVAL;

    va_start(args, out);

    while (1) {
        const char *const format = va_arg(args, const char *);

        if (!format) {
            va_end(args);
            return 0;
        }

        if (*format) {
            if (fputs(format, out) == EOF) {
                va_end(args);
                return 0;
            }
        } else
        if (format >= out_formats && format < out_formats + sizeof out_formats) {
            switch ((out_type)(format - out_formats)) {

            case out_char:
                if (fprintf(out, "%c", va_arg(args, int)) < 0) {
                    va_end(args);
                    return EIO;
                }
                break;

            case out_int:
                if (fprintf(out, "%*d", width, (int)va_arg(args, int)) < 0) {
                    va_end(args);
                    return EIO;
                }
                break;

            case out_double:
                if (fprintf(out, fixed ? "%*.*f" : "%*.*e", width, decimals, (float)va_arg(args, double)) < 0) {
                    va_end(args);
                    return EIO;
                }
                break;

            case out_FILE:
                out = va_arg(args, FILE *);
                if (!out) {
                    va_end(args);
                    return EINVAL;
                }
                break;

            case out_set_fixed:
                fixed = !!va_arg(args, int);
                break;

            case out_set_width:
                width = va_arg(args, int);
                break;

            case out_set_decimals:
                decimals = va_arg(args, int);
                break;

            case out_count:
                break;
            }
        }
    }
}

请注意,上述内容甚至缺乏OUT("string literal")支持;它的实施非常简单。

最后,main.c显示使用上述内容的示例:

#include <stdlib.h>
#include "out.h"

int main(void)
{
    double  q = 1.0e6 / 7.0;
    int     x;

    out("Hello, world!\n", OUT_END);

    out("One seventh of a million is ", OUT_decimals(3), OUT(q), " = ", OUT_fixed(1), OUT(q), ".", OUT_EOL);

    for (x = 1; x <= 9; x++)
        out(OUT(stderr), OUT(x), " ", OUT_width(2), OUT(x*x), OUT_EOL);

    return EXIT_SUCCESS;
}

在评论中,chux指出,如果我们填充out_formats数组,我们可以摆脱指针不等式比较;然后(假设,只是为了偏执狂,我们跳过零索引),我们可以使用(*format > 0 && *format < out_type_max && format == out_formats + *format)进行检查。这似乎工作得很好。

我还应用了Pascal Cuoq's answer关于如何使char *的字符串文字衰减到_Generic(),因此这支持out(OUT("literal"))。以下是修改后的out.h

#ifndef   OUT_H
#define   OUT_H 1
#include <stdio.h>

typedef enum {
    out_string = 1,
    out_int,
    out_double,
    out_set_FILE,
    out_set_fixed,
    out_set_width,
    out_set_decimals,
    out_type_max
} out_type;

extern const char out_formats[out_type_max + 1];

extern int outf(FILE *, ...);

#define out(x...) outf(stdout, x)
#define err(x...) outf(stderr, x)

#define OUT(x) _Generic( (0,x),         \
    FILE *: out_formats + out_set_FILE, \
    double: out_formats + out_double,   \
    int:    out_formats + out_int,      \
    char *: out_formats + out_string    ), (x)

#define OUT_END         ((const char *)0)
#define OUT_EOL         "\n", ((const char *)0)
#define OUT_fixed(x)    (out_formats + out_set_fixed), ((int)(x))
#define OUT_width(x)    (out_formats + out_set_width), ((int)(x))
#define OUT_decimals(x) (out_formats + out_set_decimals), ((int)(x))

#endif /* OUT_H */

以下是相应修改的实现out.c

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include "out.h"

const char out_formats[out_type_max + 1] = {
    [ out_string ] = out_string,
    [ out_int ] = out_int,
    [ out_double ] = out_double,
    [ out_set_FILE ] = out_set_FILE,
    [ out_set_fixed ] = out_set_fixed,
    [ out_set_width ] = out_set_width,
    [ out_set_decimals ] = out_set_decimals,
};

int outf(FILE *stream, ...)
{
    va_list args;

    /* State (also, stream is included in state) */
    int     fixed = 0;
    int     width = -1;
    int     decimals = -1;

    va_start(args, stream);

    while (1) {
        const char *const format = va_arg(args, const char *);

        if (!format) {
            va_end(args);
            return 0;
        }

        if (*format > 0 && *format < out_type_max && format == out_formats + (size_t)(*format)) {
            switch ((out_type)(*format)) {

            case out_string:
                {
                    const char *s = va_arg(args, char *);
                    if (s && *s) {
                        if (!stream) {
                            va_end(args);
                            return EINVAL;
                        }
                        if (fputs(s, stream) == EOF) {
                            va_end(args);
                            return EINVAL;
                        }
                    }
                }
                break;

            case out_int:
                if (!stream) {
                    va_end(args);
                    return EINVAL;
                }
                if (fprintf(stream, "%*d", width, (int)va_arg(args, int)) < 0) {
                    va_end(args);
                    return EIO;
                }
                break;

            case out_double:
                if (!stream) {
                    va_end(args);
                    return EINVAL;
                }
                if (fprintf(stream, fixed ? "%*.*f" : "%*.*e", width, decimals, va_arg(args, double)) < 0) {
                    va_end(args);
                    return EIO;
                }
                break;

            case out_set_FILE:
                stream = va_arg(args, FILE *);
                if (!stream) {
                    va_end(args);
                    return EINVAL;
                }
                break;

            case out_set_fixed:
                fixed = !!va_arg(args, int);
                break;

            case out_set_width:
                width = va_arg(args, int);
                break;

            case out_set_decimals:
                decimals = va_arg(args, int);
                break;

            case out_type_max:
                /* This is a bug. */
                break;
            }
        } else
        if (*format) {
            if (!stream) {
                va_end(args);
                return EINVAL;
            }
            if (fputs(format, stream) == EOF) {
                va_end(args);
                return EIO;
            }
        }
    }
}

如果您发现错误或有任何建议,请在评论中告诉我。我实际上并不需要这样的代码,但我确实发现非常的方法。