COBOL调用RAND模块意外修改无关变量问题排查求助
COBOL子程序调用导致未传入变量被意外修改的问题分析与解决
问题描述
本人接触COBOL仅一周,编写了石头剪刀布(RPS)游戏,将随机数生成逻辑独立为RAND模块通过CALL调用。但调用该模块时,未传入的USER-WIN、COMP-WIN变量会被意外修改。已尝试调整变量层级无效,将随机数逻辑放回主程序则运行正常;将RAND模块中UPPER-BOUND、RAND-VAL的长度改为9(15)及以下时,问题也会消失。
使用GnuCOBOL编译器,编译命令为:
cobc -m -free RAND.COBOL cobc -x -free RPS.COBOL
运行环境为Linux Mint Xfce。
原因分析
核心问题是主程序与子程序的参数长度不匹配,导致内存越界写入:
- 主程序中
UPPER-BOUND和RAND-VAL定义为PIC 9,仅占用1字节内存; - 但RAND模块的
LINKAGE SECTION中,这两个参数被定义为PIC 9(38),每个参数占用38字节内存。
当调用子程序时,COBOL会按照子程序的参数定义来读写内存:
- 子程序处理第一个参数
UPPER-BOUND时,会试图读取38字节的数据,而主程序只传入了1字节,超出的37字节会读取后续内存的垃圾数据; - 处理第二个参数
RAND-VAL时,子程序会写入38字节的数据,而主程序仅为该变量分配了1字节,超出的37字节会直接覆盖后续内存区域; - 在主程序的内存布局中,
USER-WIN、COMP-WIN刚好位于RAND变量之后,因此被越界写入的内容篡改。
修改变量长度生效的原理
当把RAND模块的参数改为9(15)及以下时,GnuCOBOL会将这些数值型变量映射为64位整数(占用8字节内存)。此时虽然主程序的参数(1字节)和子程序的参数(8字节)仍然不匹配,但越界写入的内存范围从37字节缩小到7字节,刚好没有覆盖到USER-WIN和COMP-WIN所在的内存区域,因此问题暂时消失。但这只是巧合,并非根本解决方法,后续仍可能出现其他内存越界问题。
预防措施
- 严格匹配参数定义:主程序调用时传入的变量,必须与子程序
LINKAGE SECTION中定义的参数在数据类型、长度、精度上完全一致; - 使用COPYBOOK统一参数结构:将需要传递的参数定义在COPYBOOK文件中,主程序和子程序都引用该文件,避免手动定义导致的不一致;
- 启用编译警告:编译时添加
-Wall参数(如cobc -Wall -m -free RAND.COBOL),GnuCOBOL会检测到参数长度不匹配的问题并给出警告; - 使用内存调试工具:借助
valgrind等工具运行程序,能快速定位内存越界的具体位置; - 按需选择数值长度:根据实际需求选择合适的数值类型长度,比如本场景中只需要0-2的随机数,使用
PIC 9完全足够,无需9(38)。
附程序代码
主程序(RPS.COBOL)
IDENTIFICATION DIVISION. PROGRAM-ID. ADVENT-RPS. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. 01 CHOICE. 03 USER-CHOICE PIC A(1). 03 COMP-CHOICE PIC A(1). 01 WIN. 03 COMP-WIN PIC 9 VALUE 0. 03 USER-WIN PIC 9 VALUE 0. 01 RAND. 03 UPPER-BOUND PIC 9 VALUE 3. 03 RAND-VAL PIC 9. PROCEDURE DIVISION. DISPLAY "Choose R, P, or S" PERFORM UNTIL 1 = 0 *> ACCEPT USER INPUT FROM TERMINAL AND CHECK FOR CORRECTNESS PERFORM USER-PIC *> GENERATES RANDOM COMPUTER CHOICE PERFORM COMP-PICK *> CHECKS WHO WON THE ROUND AND IF SOMEONE WON THE GAME PERFORM CHECK-WIN END-PERFORM. STOP RUN. USER-PIC. ACCEPT USER-CHOICE FROM CONSOLE. IF (USER-CHOICE NOT = "R") AND (USER-CHOICE NOT = "P") AND (USER-CHOICE NOT = "S") DISPLAY "Please choose a valid input (R, P, S)." PERFORM USER-PIC END-IF. COMP-PICK. *> CAUSING USER-WIN AND COMP-WIN TO CHANGE UNCONTROLLABLY CALL "RAND" USING UPPER-BOUND, RAND-VAL. IF RAND-VAL = 0 MOVE "R" TO COMP-CHOICE ELSE IF RAND-VAL = 1 MOVE "P" TO COMP-CHOICE ELSE MOVE "S" TO COMP-CHOICE END-IF END-IF. DISPLAY "Computer chose: " COMP-CHOICE. CHECK-WIN. IF USER-CHOICE = COMP-CHOICE DISPLAY "It's a tie!" EXIT PARAGRAPH ELSE IF (USER-CHOICE = "R" AND COMP-CHOICE = "P") OR (USER-CHOICE = "P" AND COMP-CHOICE = "S") OR (USER-CHOICE = "S" AND COMP-CHOICE = "R") DISPLAY "Computer beat you this round!" COMPUTE COMP-WIN = COMP-WIN + 1 ELSE DISPLAY "You beat the computer this round!" COMPUTE USER-WIN = USER-WIN + 1 END-IF END-IF. IF USER-WIN = 2 DISPLAY "You won!" STOP RUN. IF COMP-WIN = 2 DISPLAY "Computer won!" STOP RUN. END PROGRAM ADVENT-RPS.
RAND模块(RAND.COBOL)
IDENTIFICATION DIVISION. PROGRAM-ID. RAND. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-SEED-STRING PIC X(16). 01 WS-SEED-INT PIC 9(16). LINKAGE SECTION. 01 UPPER-BOUND PIC 9(38). 01 RAND-VAL PIC 9(38). PROCEDURE DIVISION USING UPPER-BOUND, RAND-VAL. MOVE FUNCTION CURRENT-DATE TO WS-SEED-STRING. MOVE FUNCTION NUMVAL(WS-SEED-STRING) TO WS-SEED-INT. COMPUTE RAND-VAL = FUNCTION RANDOM(WS-SEED-INT) * UPPER-BOUND. EXIT PROGRAM.
内容的提问来源于stack exchange,提问作者Ethan Krug
相关产品推荐
相关产品推荐

