iOS开发 - OC - duplicate symbol _OBJC / undefind symbol 错误的相关处理

前言: 

    作为一个iOS开发,相信大家都会遇到类似于 “duplicate symbol” 的程序报错。 对于很多新手来说,可能会有点手足无措,因为这种类型的报错一般并非是代码的逻辑错误,大部分情况下是在编译过程出错导致的,因此相对来说排查不易。在前几天,我在引用了两个SDK的过程中出现了这个问题,运用不同的手段最终解决了这个问题。今天本文就这个错误进行一个详细的分析以及如何处理做一个探讨,如果有错误的地方还请指出。

一、duplicate symbol /undefind  symbol出现的原因

duplicate symbol错误原因

      这种错误的原因多种多样,解决方式因此也并不固定,以下几种情况都会导致高错误的出现。

    1. 误将.m文件引入为头文件。  
    2. 同一个工程中含有同名的文件。(一般在多人开发过程容易出现)
    3. 引入的第三方框架包涵了与本地同名的文件
    4. 第三方框架的.o文件同名
    5. 引用了不同库文件中含有相同的方法名

undefind  symbol错误原因

    1. 相关的.a文件没有加入。
    2. 相关的.a文件中缺少对应的方法。

原因分析:

虽然这么多种类的错误原因看似复杂,实际上他们的根源是一样的。 前面说了这是在编译的阶段的错误,我们就从编译器开始分析。

Xcode3所使用的编译器是gcc编译器,而在Xcode4之后和版本中,Xcode所使用的编译器已经替换成为新的LLVM编译器。LLVM编译器的前端是clang,当然就算是现在,Xcode还是支持GCC的,不过可能需要手动下载。编译器的流程大概是下面的图的样子,不过原理大致一样,主要是有于理解,在网上看到的讲解(原文看这里):

预处理: 预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译:    将得到的i文件翻译成汇编代码。s文件。
汇编:    将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。
链接:    将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。

虽然这是针对C程序的讲解,但是作为C大家族的OC也是类似。.s文件是汇编文件,貌似Xcode的clang是没有这一步的,而是直接到了.o文件。*.o文件原来就是我们说的目标文件,是二进制的,在很多第三方库中,我们一般会看到.a或者 .framework 或者直接XXSDK, 其实这些文件中包涵的都是.o文件。 不信你可以随便找一个.a文件,然后控制台对其进行 ar -x *.a 指令, 会解压出很多.o文件, 这些都是制作者写的.m文件经过编译得到的,经过打包成了.a文件。  .o文件最终会被用来链接,才可以变成一个可执行的文件,比如iPA文件就是啦。这里要好好弄清楚链接过程了。  

链接器 (linker) 将一个个的目标文件 ( 或许还会有若干程序库 ) 链接在一起生成一个完整的可执行文件。

符号解析 (symbol resolution) 阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合 :

(1) 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;

(2) 集合 U 是未解析符号 (unresolved symbols ,比如已经被引用但是还未被定义的符号 ) 的集合;

(3) 集合 D 是所有之前已被加入到 E 的目标文件定义的符号集合。一开始, E 、 U 、 D 都是空的。

链接器的工作过程:

(1): 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中,然后处理下一个输入文件。

(2): 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中。不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点 (fixed point) ,此时 U 和 D 不再变化。而那些未加入到 E 中的 f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。

(3): 如果处理过程中 D 加入一个已存在的符号 ,或者当扫描完所有输入文件时 U 非空,链接器报错并停止动作。否则,它把 E 中的所有目标文件合并在一起生成可执行文件

文中看到 “符号”这个字眼,是不是很熟悉(看本文标题),报错指的就是symbol这个东西,它实际上将我们在程序中的全局变量名函数名类名,通过OC的消息发送方式,我们大概也联想到了,OC中通过函数名来识别函数进行相应的处理。上文中我门看到有三个集合,D集合就是合法的目标程序的符号集合,也即是我门所说的.o中的全局变量名函数名类名都在里面了。我门自己也可以查看这个表格的,方法是在控制台 对.o文件进行

nm *.o >> symbols.txt

比如我这里获取到了cJSON中的符号列表是这样的 

 

最终会生成*.o文件中所有的函数名存储在symbols.txt中。 在编译器链接的过程中,每当获取到一个符号名的时候,都会将制放入表格,如果有同名的则会报错。

现在知道为什么程序报错了吗?  

分析结果: 

    1. 误将.m文件引入为头文件。    : 当引入了.m文件,说明同时也引入了.m文件中的.h文件,编译器不管那么多,所有的函数名照单全收,于是重名了,报错!
    2. 同一个工程中含有同名的文件。(一般在多人开发过程容易出现)
    3. 引入的第三方框架包涵了与本地同名的文件:  框架中的同名文件中,一般都是开源的,里面含有相同的符号名可以理解了
    4. 第三方框架的.o文件同名                          同上
    5. 引用了不同库文件中含有相同的方法名         同上

其实这个错误说白了就是定义相同的函数名。

    1. 相关的.a文件没有加入。                          链接列表中没有的符号名 被使用 报错 
    2. 相关的.a文件中缺少对应的方法。               链接列表中没有的函数名 被使用 报错 

二、duplicate symbol /undefind  symbol错误的解决办法

现在我门知道原因了,那就开始着手解决了。

duplicate symbol解决办法

1.前面三个原因属于项目中的因素,就相对简单了,如果是.m 文件引用错误,改为h文件即可。工程中有同名文件,要不删除,要不改名字。有相通的函数名,要不删除,要不修改。搞定!

2.后两个原因是引用的库文件问题,解决稍微麻烦。

     如果是因为第三方框架的引入导致duplicate symbol 错误,那就好好看看提示,一般都会有提示告知,在某个地方含有相同的函数名(下图这样的)。

这里要分下情况,如果是使用了相同的库文件,那直接解开对应的SDK,将相同的半部分删除即可。操作流程如下:

一般的sdk都会介入不同的架构用于不同的平台上的编译,比如如果想在模拟器上编译就需要兼容 i386架构, 真机需要兼容armv7架构等。我门先要将不同的架构的文件分离出来。 上控制台console 指令 查看库文件支持的架构:

$ lipo -info FunSDK  

  打印出:   

/Users/JDiOS/Desktop/嘉德/jadeApp2/jadeApp2/FunSDK.framework/Versions/A/FunSDK are: armv7 armv7s i386 x86_64 arm64 

      说明该静态库支持的是  armv7 armv7s i386 x86_64 arm64  5个不同的架构。

      如果我们要修改的话,需要全部剥离,下面只挑选 armv7架构进行演示:

      将armv7解压出来
lipo FunSDK -thin armv7 -output Funrmv7.a

     成功之后 可以看一下文件大小的对比,大概是没有分离之前的1/5。 说明静态库.a文件其实只是一个压缩包,将多个架构的文件压缩在一起了。

 

     之前说了.a文件其实是很多.o文件的集合,我门要删除某个.o文件还要再进行分解,这里要提醒一下,先建立好一个文件夹,将获得的armv7架构库文件放进文件夹,在文件夹中处理,不然 .o文件太多,不好管理,丢失了就不好,因为处理完我门还要打包回去。指令: 

ar -x Funrmv7.a

可以看到很多 .o文件 。

 

     这时候找到找到要删除的.o 将之删除即可。 也可以指令 

rm cJSON.o     //删除的是 cJSON.o 文件

     删除完毕之后,就该合并回去了。一步一步来,想将所有得.o文件打包回去

libtool -static -o ../new-armv7.a *.o    //将所有的o文件打包成new-armv7.a  

     刚才之将armv7一个架构的文件处理完,要完全做完,其它的架构也要处理。 最终 获得所有的架构的文件合并成静态库 指令

lipo -create -output FunSDKnew armv76.a  i386.a arm64.a armv7s.a  x86_64.a

    最后将FunSDKnew改名 FunSDK(你的项目中的原来的SDK的名字)  覆盖原来项目中的SDK即可。

注意点:

      如果是名字相同的当然可以这么处理, 可是,我门已经分析过,.o文件冲突的直接原因不是文件本身,而是文件中包含的符号。如果SDK对cJSON文件的内容进了更改,那么刚才的方法虽然可以避免符号冲突,但是,整个文件被删除,修改的内容也被删了,最终可能导致SDK部分功能没了, 这当然不是我们想看到的。实际上,我遇上的就是这个问题, 最后编译过程中,因为直接删除了FunSDK中的文件,导致符号缺失 ,像这样:

 

 

      是不是看到什么了,没错,也是本文说的一个点 undefind  symbol 错误。

      原来虽然cJSON是开源的,但是FunSDK 在开源的基础上做了修改,直接删除cJSON等于把FunSDK新增的setStringValue的函数给删掉了,预编译器又报错!  怎么办?

请看:

undefind  symbol 解决办法

两个办法:

  1. 找SDK供应方 要.o文件的源代码,修改之后打包回去      (呵呵,源代码有点难啊)。 最好还是跟供应商协商修改。
  2. 一个取巧的办法  (这是我无意中发现的)   一般SDK有冲突的文件大部分原因是因为打包相同的开源文件,去网上都可down的到,下载过来,将里面的缺失的方法(符号)添加,放到项目中,这样可以覆盖库中的方法。  但是有一定的风险,运气好可能可以避免,因为你的项目中不一定会用到SDK中所有的方法,恰巧你库中缺失的方法就是不需要用到的,那么只需要写入个方法,不需要实现什么,编译过程可以通过。 解决!
  3. 缺失库文件嘛,看看是不是target -> bulid Phases 中加入对应的.a 文件(主要还是这个原因吧)

三、其它的方法 解决duplicate symbol问题

     是不是每次出现这种问题都需要进行上上述的方案来解决呢? 不一定。

     我门已经知道这是在编译链接的时候出现的错误,Xcode在链接的时候,提供了一个可选的链接方式。

     项目中: target -> bulid setting -> link -> oteher link flags   

     这里可以使用的库链接方式有:  -all_load    -Objc    -force_load      -dead_strip 等等。

    为了解决duplicate symbol的问题,以下链接方式可以使用:

  • 设置-dead_strip之后将会是项目忽略掉重复的符号, 最终编译会通过的,但是如果就像之前所说的,忽略的东西如果被修改过,有部分可能导致SDK的部分API不能使用。
  •     -force_load也可以通过编译,这种方式是强制链接静态库。 需要指定链接的库文件

    在大部分情况下,使用-dead_strip 和  -force_load链接方式是可行的,毕竟比上面的方法要轻松无数倍不是吗?如果没什么特别的要求,建议使用这个方式。

    附一张链接方式的含义。      

      

     

 

 

  

      

 

转载于:https://www.cnblogs.com/FBiOSBlog/p/5920914.html

报错:15:14:55.528 INFO NativeLibraryLoader - Loading libgkl_compression.so from jar:file:/public/home/xiaoshunpeng/miniconda3/envs/chip-seq/share/picard-3.4.0-0/picard.jar!/com/intel/gkl/native/libgkl_compression.so [Sat Jul 26 15:14:55 CST 2025] MarkDuplicates --INPUT /public/home/xiaoshunpeng/chip_samtools/P-T-H-K16-1/paired_aligned_sorted.bam --OUTPUT /public/home/xiaoshunpeng/chip_picard/P-T-H-K16-1/picard-1.bam --METRICS_FILE /public/home/xiaoshunpeng/chip_picard/P-T-H-K16-1/picard-1.metrics --REMOVE_DUPLICATES true --ASSUME_SORTED true --CREATE_INDEX true --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX <optimized capture of last three ':' separated fields as numeric values> --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false
最新发布
07-27
<think>我们注意到报错信息中包含了大量的默认参数,这些参数在命令行中并未指定,但是被自动添加了。这可能是由于Picard工具内部设置的默认参数。然而,从报错信息来看,并没有明显的错误,只是显示了运行的参数配置。但是用户可能遇到了程序运行失败或者未达到预期的情况。 但是,根据之前的问题,用户可能是在参数格式上遇到了问题。之前的警告是混合使用了新旧参数格式,现在用户已经统一使用新格式(长选项),但是运行后出现了大量的默认参数输出,这通常是Picard在INFO级别日志中打印的。 如果程序运行成功,则不需要担心;如果运行失败,我们需要查看具体的错误信息。然而,用户提供的日志中没有错误,只有INFO信息。 但是,请注意:在提供的命令中,有一个参数`--ASSUME_SORTED`,在Picard MarkDuplicates中,正确的参数名应该是`--ASSUME_SORT_ORDER`(指定排序顺序,如coordinate, queryname等)或者`--ASSUME_SORTED`(布尔值,表示是否假设已排序,但已过时)。在Picard 3.4.0版本中,推荐使用`--ASSUME_SORT_ORDER`。 因此,我们可能需要将`--ASSUME_SORTED true`改为`--ASSUME_SORT_ORDER coordinate`(假设输入文件是按坐标排序的,这是常见的)。 另外,注意`--REMOVE_DUPLICATES`参数,如果设置为true,则会实际删除重复的reads,而不是仅仅标记。 如果用户希望按照坐标排序的BAM文件进行去重,那么应该使用`--ASSUME_SORT_ORDER coordinate`。 修改后的命令如下:</think>### 解决 Picard MarkDuplicates 日志信息问题 您提供的不是错误信息,而是 Picard 工具的标准运行日志。这些信息表示程序正在正常执行,显示了加载的库和完整的参数配置。以下是关键分析和优化建议: #### 1. 日志信息解析 ```plaintext 15:14:55.528 INFO NativeLibraryLoader - Loading libgkl_compression.so ``` - **含义**:Picard 正在加载 Intel Genomics Kernel Library (GKL) 压缩库 - **作用**:加速 BAM 文件的压缩/解压缩操作 - **正常性**:这是标准启动过程,非错误 ```plaintext [Sat Jul 26 15:14:55 CST 2025] MarkDuplicates --INPUT ... --OUTPUT ... ``` - **含义**:显示完整的命令行参数配置 - **作用**:提供执行环境的完整记录,便于调试 - **包含**:所有显式设置的参数 + 默认参数值 #### 2. 优化参数配置建议 当前命令: ```bash picard MarkDuplicates \ --INPUT /path/to/input.bam \ --OUTPUT /path/to/output.bam \ --METRICS_FILE /path/to/metrics.txt \ --REMOVE_DUPLICATES true \ --ASSUME_SORTED true \ --CREATE_INDEX true ``` 优化建议: ```bash picard MarkDuplicates \ --INPUT /path/to/input.bam \ --OUTPUT /path/to/output.bam \ --METRICS_FILE /path/to/metrics.txt \ --REMOVE_DUPLICATES true \ --ASSUME_SORT_ORDER coordinate \ # 更精确的排序假设 --CREATE_INDEX true \ --VALIDATION_STRINGENCY LENIENT \ # 处理格式不规范数据 --TAGGING_POLICY All \ # 标记所有重复reads --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 # 光学重复检测 ``` #### 3. 关键参数解释 | 参数 | 当前值 | 推荐值 | 说明 | |------|--------|--------|------| | `ASSUME_SORTED` | true | `--ASSUME_SORT_ORDER coordinate` | 更精确指定排序类型 | | `VALIDATION_STRINGENCY` | (默认STRICT) | LENIENT | 宽松处理格式问题 | | `TAGGING_POLICY` | (默认DontTag) | All | 标记重复reads | | `MAX_RECORDS_IN_RAM` | 500000 | 根据内存调整 | 控制内存使用 | | `COMPRESSION_LEVEL` | 5 | 1 (最快) 或 9 (最小) | 压缩级别平衡 | #### 4. 内存优化配置 ```bash # 示例:针对大文件的优化配置 picard -Xmx8G MarkDuplicates \ # 分配8GB内存 --INPUT large_file.bam \ --OUTPUT deduped.bam \ --METRICS_FILE metrics.txt \ --ASSUME_SORT_ORDER coordinate \ --CREATE_INDEX true \ --MAX_RECORDS_IN_RAM 1000000 \ # 增加内存记录数 --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 \ --TAGGING_POLICY OpticalOnly \ # 只标记光学重复 --COMPRESSION_LEVEL 1 # 最快压缩 ``` #### 5. 完整执行监控脚本 ```bash #!/bin/bash # picard_monitor.sh INPUT_BAM="/public/home/.../paired_aligned_sorted.bam" OUTPUT_BAM="/public/home/.../picard-1.bam" METRICS_FILE="/public/home/.../picard-1.metrics" LOG_FILE="/public/home/.../picard_log.txt" # 启动时间记录 start_time=$(date +%s) # 执行Picard并记录资源使用 /usr/bin/time -v -o time.log picard MarkDuplicates \ --INPUT "$INPUT_BAM" \ --OUTPUT "$OUTPUT_BAM" \ --METRICS_FILE "$METRICS_FILE" \ --REMOVE_DUPLICATES true \ --ASSUME_SORT_ORDER coordinate \ --CREATE_INDEX true \ --VALIDATION_STRINGENCY LENIENT \ --TAGGING_POLICY All \ 2>&1 | tee "$LOG_FILE" # 结束时间记录 end_time=$(date +%s) # 生成报告 echo "===== PROCESSING REPORT =====" echo "Start time: $(date -d @$start_time)" echo "End time: $(date -d @$end_time)" echo "Duration: $((end_time - start_time)) seconds" # 添加资源使用报告 cat time.log rm time.log # 验证输出 echo -e "\n===== OUTPUT VERIFICATION =====" ls -lh "$OUTPUT_BAM" samtools quickcheck "$OUTPUT_BAM" && echo "BAM file is valid" ``` ### 常见问题解答 **Q: 为什么显示这么多参数?** A: Picard 默认打印完整的参数配置,包括: - 用户显式设置的参数 - 所有默认参数值 - 内部优化参数(如 `READ_NAME_REGEX`) **Q: 如何减少日志输出?** ```bash # 添加日志控制参数 picard MarkDuplicates \ ... \ --VERBOSITY ERROR \ # 只显示错误 --QUIET true # 完全静默模式 ``` **Q: 如何确认程序成功执行?** 1. 检查退出代码:`echo $?` (0表示成功) 2. 验证输出文件存在且非空 3. 检查日志结尾是否有成功信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值