我有一个在这样的公共结构中声明的数组:
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++;
}
答案 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( ®isters, 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,它提供了自增长数组和更多功能。