由于分配无效而导致SegFault?

时间:2018-09-29 16:34:25

标签: c

我有一个在这样的公共结构中声明的数组:

uint16_t *registers;

在一个函数中,我要检索一个char字符串(存储在缓冲区中,请参见下面的代码),该字符串包含用逗号分隔的数值(例如“ 1,12,0,136,5,76,1243”)。我的目标是获取每个单独的数值并将其依次存储在数组中。

i = 0;
const char delimiter[] = ",";
char *end;

tmp.vals = strtok(buffer, delimiter);
while (tmp.vals != NULL) {
    tmp.registers[i] = strtol(tmp.vals, &end, 10);
    tmp.vals = strtok(NULL, delimiter);
    i++;
}

问题是包含strtol的行产生了分段错误(核心转储)错误。我很确定这是由于尝试将无符号的长值放入uint16_t数组插槽而引起的,但是无论如何我都无法解决它。


按如下所示更改代码似乎已经解决了问题:

unsigned long num = 0;
size_t size = 0;
i = 0;

size = 1;
tmp.vals = (char *)calloc(strlen(buffer) + 1, sizeof(char));
tmp.registers = (uint16_t *)calloc(size, sizeof(uint16_t));
tmp.vals = strtok(buffer, delimiter);
while (tmp.vals != NULL) {
    num = strtoul(tmp.vals, &end, 10);
    if (0 <= num && num < 65536) {
        tmp.registers = (uint16_t *)realloc(tmp.registers, size + i);
        tmp.registers[i] = (uint16_t)num;
    } else {
        fprintf(stderr, "==> %lu is too large to fit in register[%d]\n", num, i);
    }
    tmp.vals = strtok(NULL, delimiter);
    i++;
}

1 个答案:

答案 0 :(得分:2)

一个long integer is at least 32 bits,所以,您将丢失信息,试图将有符号的32位整数转换为无符号的16位整数。如果您有编译器警告(我使用-Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic),则应该告诉您。

test.c:20:28: warning: implicit conversion loses integer precision: 'long' to 'uint16_t'
      (aka 'unsigned short') [-Wconversion]
        tmp.registers[i] = strtol(tmp.vals, &end, 10);
                         ~ ^~~~~~~~~~~~~~~~~~~~~~~~~~

但是,这不会导致段错误。您将丢失16位,并且符号更改会带来有趣的事情。

#include <stdio.h>
#include <inttypes.h>

int main() {
    long big = 1234567;
    uint16_t small = big;
    printf("big = %ld, small = %" PRIu16 "\n", big, small);
}

如果您知道所读内容可以容纳16位,则可以先使用strtoul读取 unsigned < / em> long,验证它是否足够小,然后进行显式投射。

    unsigned long num = strtoul(tmp.vals, &end, 10);
    if( 0 <= num && num < 65536 ) {
        tmp.registers[i] = (uint16_t)num;
    }
    else {
        fprintf(stderr, "%lu is too large to fit in the register\n", num);
    }

未正确初始化tmp.registers(可能还有buffer),并没有将点分配给垃圾。如果您像这样简单地在堆栈上声明了tmp

Registers tmp;

这只会为tmp分配内存,而不是它指向的内容。它将包含垃圾。 tmp.registers将指向内存中的某个随机点。当您尝试对其进行写操作时,它将最终出现段错误。

需要分配寄存器阵列。

size_t how_many = 10;
uint16_t *registers = malloc( sizeof(uint16_t) * how_many );
Thing tmp = {
    .registers = registers,
    .vals = NULL
};

这很好,只要您的循环仅运行how_many次即可。但是您在阅读输入时无法确定这一点。您的循环可能正在读取无限数量的寄存器。如果超过我们分配的10个,它将再次开始写入其他人的内存和段错误。

这里,动态内存不是一个太大的话题,但是我们至少可以通过跟踪registers的最大大小以及它的大小来将循环限制为数组的大小。我们可以在循环中执行此操作,但它确实属于该结构。

typedef struct {
    uint16_t *registers;
    char *vals;
    size_t max;
    size_t size;
} Registers;

在处理此问题时,请将初始化放入函数中,以便确保每次都可靠地完成。

void Registers_init( Registers *registers, size_t size ) {
    registers->registers = malloc( sizeof(uint16_t) * size );
    registers->max = size;
    registers->size = 0;
}

与我们的边界检查相同。

void Registers_push( Registers *registers, uint16_t num ) {
    if( registers->size == registers->max ) {
        fprintf(stderr, "Register has reached its limit of %zu\n", registers->max);
        exit(1);
    }
    registers->registers[ registers->size ] = (uint16_t)num;
    registers->size++;
}

现在我们可以安全地添加寄存器了。至少它会很好地出错。

Registers registers;
Registers_init( &registers, 10 );

tmp.vals = strtok(buffer, delimiter);
while (tmp.vals != NULL) {
    unsigned long num = strtoul(tmp.vals, &end, 10);
    if( 0 <= num && num < 65536 ) {
        Registers_push( &tmp, (uint16_t)num );
    }
    else {
        fprintf(stderr, "%lu is too large to fit in the register\n", num);
    } 
    tmp.vals = strtok(NULL, delimiter);
    i++;
}

这时,我们正在重新实现一个受大小限制的数组。这是一个很好的练习,但是对于生产代码use an existing library such as GLib,它提供了自增长数组和更多功能。