两个printfs以不同方式打印相同的字符串

Rub*_*ben 7 c linux gcc pointers i386

我正在尝试创建一个处理大整数运算的库.大整数存储在结构中:

typedef struct BigInt BigInt;
struct BigInt
{
    uint32_t size;
    uint32_t *data;
};
Run Code Online (Sandbox Code Playgroud)

第一个成员是包含数字长度的uint32_t,第二个成员是指向实际数字数据的指针(存储在二进制补码中).我编写了一个简单的toHex(BigInt*a)函数来分配内存,将大整数的十六进制值打印到字符串中,然后返回地址.

在我的主循环中,我有以下内容:

int main(int argc, char *argv[])
{
    char *ap, *bp;
    BigInt *a = fromUInt32(0x7fffffff), *b = fromUInt32(1), *c = fromUInt32(0x80000000);
    _add(a, b);
    ap = toHex(a);
    bp = toHex(c);
    printf("%s\n", ap);
    printf("%s\n%s\n", ap, bp);
    printf("%s\n%s\n", ap, bp);
    free(ap);
    free(bp);
    deleteBigInt(a);
    deleteBigInt(b);
    deleteBigInt(c);
}
Run Code Online (Sandbox Code Playgroud)

好奇的是,打印出来的

0000000080000000
0
0000000080000000
0000000080000000
0000000080000000
Run Code Online (Sandbox Code Playgroud)

因此,第二个printf语句打印的内容与第一个和第三个printf语句不同.似乎第一个printf语句是正确的,第二个是搞乱的.我已经使用GDB逐步完成了代码,在评估了toHex之后,ap指向字符串"0000000080000000",由空指针终止.

我完全不知所措.据我所知,可能性是:
1.由于一些奇怪的原因,我遇到了未定义的行为.
2.在_add中我调用用x86汇编代码编写的例程,其中可能存在错误(但我通过保留esi,edi,ebx,ebp和esp来遵守GCC的调用约定).
3. printf中有一个错误,似乎不太可能.

此外,由于没有释放toHex分配的内存,我有一个明显的"内存泄漏"(引用因为对内存泄漏的看法似乎有所不同),但这并不重要.

我的toHex功能是Sourav Ghosh要求的,如下:

char numToHex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
char *toHex(BigInt *a)
{
    char *result, *ptr;
    // allocate enough space for 8 characters for each uint32_t and 1 terminating 0
    ptr = result = malloc(a->size * 8 + 1);
    // loop over the uint32_t's stored in a->data
    // (there are a->size of them)
    for (uint32_t i = 0; i < a->size; i++)
        // parse 8 blocks of 4 bits
        for (uint32_t j = 0; j < 8; j++)
            // grab the right bits and convert them to a hex digit
            *(ptr++) = numToHex[(a->data[i] >> ((7 - j) * 4)) & 0xf];
    // add a terminating zero byte
    *ptr = 0;
    return result;
}
Run Code Online (Sandbox Code Playgroud)

我已经在~100行C + ~70行组装的程序中隔离了这种奇怪的行为.编译可以完成

nasm -f elf -s <AssemblyName>.asm
gcc <CFile>.c <AssemblyName>.o -o <OutputProgram> -m32 -std=c99 -g
Run Code Online (Sandbox Code Playgroud)

代码是未注释的,适用于想要自己检查行为的人.

编辑:Jan Spurny和Matt McNabb敦促我使用Valgrind.Valgrind说:0x40A5685:vfprintf(vfprintf.c:1655)的读取大小为1 0x40AA7FE:printf(printf.c:34)0x4075904 :(低于主要)(libc-start.c:260)地址0x42121af为1大小为17的块在0x40299D8之前分配的字节:malloc(在/usr/lib/valgrind/vgpreload_memcheck-x86-linux.so中)由0x804887D:toHex(weird.c:107)由0x8048565:main(weird.c) :30)

但这没有意义,因为我将结果设置为malloc inHex,并且之后没有改变任何东西.我现在的赌注是,某些寄存器在汇编函数中被破坏了.Edit2:用GDB检查后,我发现没有寄存器被破坏.我仍然无能为力.

M.M*_*M.M 6

reduce函数有一个错误:

while (i < a->size && !(a->data[i])) i++;
if (a->data[i] & SIGNBIT) i--;
Run Code Online (Sandbox Code Playgroud)

如果i < a->size条件被命中,则a->data[i]访问越界,导致未定义的行为.另一个分支也reduce有同样的问题


_add函数中存在一个错误(尽管在您的测试用例中没有触发):

void *k = realloc(a->data, b->size * 4);
memmove((void *)(a->data + displacement), (void *)a->data, a->size * 4);
// ....other code using `a->data`
Run Code Online (Sandbox Code Playgroud)

之后realloc,a->data变得不确定,因此导致未定义的行为使用它.这可以解释您的症状,因为未来的分配可能会重新使用a->data仍然指向的相同释放块.

也许你打算a->data = k;在这之后也有一条线?


要获得调试代码的良好帮助,如果您可以执行以下操作将会很棒:

  • 检查所有*alloc-family函数的结果,如果NULL返回则退出.否则你会得到未定义的行为(期望segfault不可靠).
  • 用C重写汇编函数.出于多种原因(调试,代码可移植性,优化),这是一个好主意.甚至可能会发现gcc -O3生成的代码比手写版本更快; 这就是编译器擅长的.
  • 检查调用的结果newAddress检查它实际返回您在测试用例中的预期.

  • 您显然关注性能,因此我可以建议重新设计,以便BigInt的数据元素以小端顺序排列.然后"溢出"和"减少"你只是追加到最后,或减少`大小`; 你不需要做'memmove`. (2认同)

Rub*_*ben 1

我在 forum.osdev.org 上发了一个帖子(那里有一些非常聪明的人),jnc100 通知我 ABI 期望在调用函数时清除方向标志。我在汇编例程中设置了方向标志(在_add中调用),并且确实在汇编例程中清除解决了问题。