通过静态库链接时功能无法正常工作,但如果复制到 prog 中则可以正常工作

functions not working well when linked via a static lib, but working if copied in the prog

我有一个静态库 (.a),其中包含我在另一个程序中使用的一些有用函数。 link 运行良好,找到了 lib 中的函数,但是当程序执行时,它运行不正常。另一方面,如果我直接在我的程序代码中 copy/paste 我需要的 lib 函数,它运行良好。而且,它在 win32 上运行良好,但现在我在 Win64 上。

编辑:我知道代码很糟糕(这不是我的),但直接复制到程序中时它可以正常工作,这意味着开发人员不会对其进行任何更改。我需要的是理解为什么当我 link 函数所在的库时它不能很好地工作,当它在 Linux64 和 Win32 上完美运行时。您可能会在这段代码中发现很多问题,但这只是一个例子;因为它没有解释为什么它在 prog 中工作而不是由 lib linked,所以它对我来说毫无用处,因为开发者根本不关心。

这是库中有问题的函数之一(我采用了最简单的,它不太依赖于库,但我怀疑其他函数无法按预期工作):

#if defined(mingwx64)
typedef long long         I64;
#else
typedef long              I64;
#endif
typedef unsigned char     UI8;

void readParamFile (char* filename, UI8* *Params, I64 *ParamsLen){
    FILE* fic;
    char* buf; int buf_Len;
    *Params=NULL; *ParamsLen=0;

    if(!(fic=fopen(filename, "rb"))){
        fprintf(stderr, "Error, can't open %s\n", filename);
        exit (-1);
    }
    fseek(fic, 0, SEEK_END);
    buf_Len=ftell(fic);
    fseek(fic, 0, SEEK_SET);

    if(!(buf=(char*)PtrAlloc(sizeof(char)*buf_Len))){
        fprintf(stderr, "Error: Can't allocate %d bytes", buf_Len);
        exit(-1);
    }

//the files are specifics, i read the lines until the first one which doesn't start with #
    do{
       if(!fgets(buf, buf_Len, fic)){
          fprintf(stderr,"Error: Can't read parameters");
          exit(-1);
       }
       buf[strlen(buf)-1]='[=10=]';
    } while (buf[0]=='#');

    if(!(*Params= (UI8*)PtrAlloc(sizeof(UI8)*strlen(buf)))){
        fprintf(stderr,"Error: can't allocate %d butes", buf_Len);
        exit(-1);
    }

    strncpy((char*) *Params , buf, *ParamsLen=strlen(buf));

    PtrFree(buf);
    fclose(fic);
}

因此,此功能在 MyLib.a 中。我使用 Mingw64 在 Win64 上编译它(就像我在 Win32 上使用 Mingw 一样),编译工作正常。 然后,在编译 MyProg 时,我 link 这个库。编译顺利。 当我启动 MyProg 时,它会在某个时候使用此功能,并在 strncpy 处停止(之前使用 memcpy,但并没有更好)。它不会抛出任何错误消息,什么也没有:我等了一会儿,程序停止了,就好像它已经把所有事情都做好了,除了它没有。如果我尝试在此 strncpy 之后进行打印,它永远不会进行打印,但 prog 结束 "normally"(我看不到崩溃)。

奇怪的是,如果我在 MyProg 的代码中 copy/paste 这个函数,假设我称它为 readFileBis,那么 readFileBis 就可以正常工作。

但是,我不会 copy/paste 每个需要它的 prog 中我的 lib 的每个函数。会很浪费。

编辑:试图为它制作一个可重现的例子,但仍在努力。所以假设这是 MyProg 的唯一文件:

MyProg.c

#define FILENAME "MyDir/ParamFile.txt"

void readParamFileBis (char* filename, UI8* *Params, I64 *ParamsLen){
    //the exact same thing as in readFile, don't wanna make the question too long.
}

int main(int argc, char *argv[]){

    UI8 *Params = NULL; 
    I64 ParamsLen = 0;

//this is working:
    readParamFileBis (FILENAME, &Params, &ParamsLen);
//this is not working : 
    readParamFile (FILENAME, &Params, &ParamsLen);

    exit(0);
}

和ParamFile.txt看起来像(数字更长):

# Introduction text
000500000000064F1B58372A27

我想知道这里可能出了什么问题。知道它在 Win32 上运行良好,我猜它与 64 位有关,但我不知道是什么。由于我在win64上重新编译了lib和prog,应该没问题。我不知道是怎么回事。

哦,还有一件奇怪的事情可能与它有关也可能无关:编译时,我可能会使用标志 -D${ARCHI}。在 Win32 上,ARCHI 是 mnigw386;在 win64 上,它是 mingwx64。当我在 Win64 上使用这个标志编译时,我收到了一些关于我的打印格式的警告(比如,我正在使用 %ld 打印一些 I64,但它不喜欢它)我在 win32 上没有。

EDIT/Solution: 所以确实是I64的问题。我设法通过 int64_t 更改它并且它正在工作。此外,我设法重新设置 Makefile 的字体(从 250 行和 16 个目标到 90 行和 5 个目标......),然后发现该体系结构确实已定义......但未包含在任何编译标志中。所以我猜 prog 看不到 I64 被定义为 long long(因为它在 if mingwx64 中)并认为它是 long,那是这里的大概率。

Windows 是 LLP64 不是 LP64;只有在针对 x64 进行编译并且高位作为垃圾传递时,您的 I64 才最终只有 32 位。

执行 #include <stdint.h> 并使用 int64_tuint64_t 等类型来避免此类错误。

不更正错误的定义是行不通的。如果必须,请使用应用补丁的构建过程,但使用工作类型定义。

发布的代码片段中存在多个问题:

  • #define FILENAME "MyDir\ParamFile.txt" 不正确。您应该将反斜杠转义为 #define FILENAME "MyDir\ParamFile.txt" 或使用适用于所有 Windows 版本的普通正斜杠:#define FILENAME "MyDir/ParamFile.txt"

  • 任意定义类型I64是有风险的。您应该包括 <stdint.h> 并使用 typedef uint64_t I64;

  • 函数原型void readFile(char* filename, UI8 *Params, I64 *ParamsLen)与预期不一致API:readFile读取文件内容并存储指向长度为[=的已分配缓冲区的指针19=] 变成 *Params。原型应该是:

    int readFile(const char *filename, UI8 **Params, I64 *ParamsLen);
    

    readFile 应该 return 一个错误代码,以防文件无法打开或读取。

  • 函数readFile中的exit (-1);后面少了一个}

  • 应该为临时缓冲区分配一个额外的字节,以防文件包含没有以换行符结尾的单行。

  • fgets() 不一定在数组末尾存储 '\n',特别是如果文件不包含一个。无条件地剥离最后一个字符是不正确的。

  • 为什么不使用 malloc() 而不是不可移植的 Windows 具体 PtrAlloc().

  • 如果您希望将文件内容作为C字符串使用,您应该为空终止符分配一个额外的字节并将其存储在数组的末尾。

此函数使用扭曲的方法从文件中读取一行。您应该更加努力地理解它的作用并从上述评论中修复它。 C 是一种敏锐而无情的语言,如果没有很好地理解所使用的元素的编程会导致错误和失败,并带来最大的挫败感。

此代码及其周围的主要问题是类型 I64 的定义。由于大量不可移植的 Win32 代码假定 long 正好是 32 位,Microsoft 决定在 Win64 上保留 long 32 位,并且更糟糕的是延迟了对标准 64 位的支持C 库中的类型,使得 %lld 不可移植到 Microsoft Windows 默认库。这解释了您从 gcc 编译器收到的警告,该编译器检查 printf 参数与格式字符串的一致性。如果它 在 win32 版本中工作 ,他们可能根本没有使用 64 位值 (long long)。

静态库与将函数代码嵌入程序之间的不同行为的一个可能解释是使用了不同的编译器、编译器选项或命令行定义。这些你都检查了吗?您是自己编译库还是从其他团队以二进制形式获取它?

在 32 位和 64 位体系结构之间移植软件并非易事,尤其是在 Windows 鼓励非可移植结构并具有不稳定的 C99 支持的平台上。

代码不仅写得不好,函数的原型也不正确:Params不是UI8 *,应该是UI8 **,函数works 偶然,它很容易坏掉。如果 developper 对此没有任何改变,我建议一些可能的解决方案:

  • 将函数复制到本地,修复问题,不要使用伪库,
  • 编写自己的函数。
  • 在另一家公司找到一份更好的工作:你在那里是在浪费时间,你会从一个更健全的环境中学到更多东西。

EDIT - 此答案的第一部分指的是编辑前的问题。最初发布的问题有 UI8 *Param 而不是 UI8* *Param

============================================= ===========================

but it IS working when directly copied in the prog,

您还没有真正确定它是否正常工作。 "Doesn't appear to fail" 与 "working properly" 不同。

给定

void readParamFile (char* filename, UI8 *Params, I64 *ParamsLen){

这段代码是错误的:

*Params=NULL

因为 *Params 是一个 unsigned char 值,而不是可以容纳 NULL.

的指针

所以这段代码也是错误的:

*Params= (UI8*)PtrAlloc(sizeof(UI8)*strlen(buf))

同样,*Params 是单个 unsigned char 值。

这毫无疑问 - 给定变量的 UI8 *Params 定义,将任何值的指针分配给 *Params 都是错误的。这是不正确的代码和未定义的行为。

代码 出现 好像它更适合

void readParamFile (char* filename, UI8 **Params, I64 *ParamsLen)

声明。

发布的代码依赖于充其量有问题的 typedef,并且具有不需要的功能 - 例如,不需要获取文件长度。

像这样的东西更简单,不依赖于非标准 typedef,并且应该工作得更好:

#include <stdio.h>
#include <string.h>
#include <stdint.h>

int readParamFileBis( const char *filename, unsigned char **params, uint64_t *paramsLen )
{
    *params = NULL;

    FILE *fp = fopen( filename, "r" );
    if ( fp == NULL )
    {
        return( -1 );
    }

    // assume we have POSIX getline()
    // (Windows implementations abound...)
    unsigned char *line = NULL;
    size_t lineLen = 0UL;

    // loop until a line that doesn't start with # is found
    for ( ;; )
    {
        ssize_t bytesRead = getline( &line, &lineLen, fp );

        // EOF - no more to read
        if ( bytesRead == -1 )
        {
            break;
        }

        // line starts with # - skip it
        if ( line[ 0 ] == '#' )
        {
            continue;
        }

        // we found the first line that
        // doesn't start with #, so pass
        // it back to the caller

        // strip any trailing newline
        line[ strcspn( "\n" ) ] = '[=14=]';

        // assume we have POSIX strdup()
        // (could just do *params = line; but
        // line could now refer to a vary large buffer
        // if a long comment line was in the file)
        *params = strdup( line );

        // tell the caller how long the line is
        // (extraneous as it's a string, but...)
        *ParamsLen = strlen( line );

        free( line );

        // found the line, so break the loop
        break;
    }

    fclose( fp );

    // didn't find a line that doesn't start with '#'
    if ( *params == NULL )
    {
         return( -1 );
    }

    // success
    return( 0 );
} 

对于 Windows,不难找到 getline()strdup() 实现。

更简单 - return 字符串(已简化并删除注释以使其尽可能短):

char *readParamFile( char* filename )
{
    FILE *fp = fopen( filename, "r" );
    if ( fp == NULL ) return( NULL );

    unsigned char *line = NULL;
    size_t lineLen = 0UL;

    for ( ;; )
    {
        ssize_t bytesRead = getline( &line, &lineLen, fp );
        if ( bytesRead == -1 ) break;
        if ( line[ 0 ] == '#' ) continue;

        line[ strcspn( "\n" ) ] = '[=15=]';
        break;
    }

    fclose( fp );

    // oops!  Don't return a comment line!
    // could also strdup( line ) in loop above after finding proper line
    if ( line != NULL && line[ 0 ] == '#' )
    {
        free( line );
        line = NULL:
    }
    return( line );
} 

这就是您真正需要找到不以 '#' 字符开头的第一行并将其 return 发送给调用者的所有内容。它将 return 指向行的指针,该行稍后必须 free() 成功,或 NULL 失败。无需指针间接寻址。