You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言:从文件读取数据生成Language结构体数组并返回(修复SIGSEGV错误)

段错误(SIGSEGV)排查与修复:读取语言文件生成结构体数组

问题背景

编写language.c中的get_available_languages()函数,期望实现以下逻辑:

  • 统计data/languages.txt的行数,每行对应创建一个Language结构体元素存入数组
  • 用strtok()按=分割每行字符串,将第一部分赋值给lang_code,第二部分赋值给lang_name
  • 完成读取后返回结构体数组
    但运行程序时触发SIGSEGV段错误。

代码文件

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "language.h"

int i;

int main ()
{
  puts ("--- Welcome to PiluX CLI installer ---");
  puts ("Language: ");
  // char* lang_name = get_language_name_from_code ("en");
  // char* lang_code = get_language_code_from_name ("English");
  // printf ("%s\n", lang_name);
  // printf ("%s\n", lang_code);
  Language *languages = get_available_languages ();
  printf ("%s\n", languages[0].name);
  free (languages);

  return 0;
}

language.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <piluxsetup/language.h>


FILE* _open_languages_file ()
{
  /* 
    data/languages.txt dosyasını açar.
    Kodun tekrar tekrar yazılmaması için oluşturuldu.
  */
  FILE *supported_languages = fopen ("data/languages.txt", "r");
  
  if (!supported_languages)
  {
    printf ("\033[1;91mhata:\033[0m Language list can't open: %s\n", strerror(errno));
    exit (1);
  }

  return supported_languages;
}

char* get_language_name_from_code (char* code)
{
  /*
    Dil kodları arasından en yakın eşleşmeyi döndürür.
    Örnek:
    get_language_from_code ("de_DE"); -> Deutsh
  */

  FILE *supported_languages = _open_languages_file ();

  char *line;
  while (fgets (line, 80, supported_languages) != NULL)
  {
    if (strstr (line, code))
    {
      char* tokenizer = strtok (line, "="); // Birinci elemanı döndürür.
      return tokenizer;
      fclose(supported_languages);
    }
  }
  fclose(supported_languages);
  return "(not specified)";
}

char* get_language_code_from_name (char* name)
{
  /*
    Dil adları arasından en yakın eşleşmeyi döndürür.
    Örnek:
    get_language_from_name ("Türkçe"); -> tr_TR
  */

  FILE *supported_languages = _open_languages_file ();

  char *line;
  while (fgets (line, 80, supported_languages) != NULL)
  {
    if (strstr (line, name))
    {
      char* tokenizer = strtok (line, "=");
      tokenizer = strtok (NULL, "="); // sonraki parçayı işle
      tokenizer[strlen (tokenizer) - 1] = '\0'; // newline karakterini sil
      fclose(supported_languages);
      return tokenizer;
    }
  }
  fclose(supported_languages);
  return "(not specified)";
}

Language* get_available_languages ()
{
  FILE *supported_languages = _open_languages_file ();

  int line_count = 0;
  char line[80];

  char* lang_code = "";
  char* lang_name = "";
  Language* languages = malloc (sizeof (Language)*2);

  while (fgets (line, 80, supported_languages) != NULL)
  {
    ++line_count;
    char* tokenizer = strtok (line, "=");
    lang_code = tokenizer;
    tokenizer = strtok (NULL, "="); // sonraki parçayı işle
    lang_name = tokenizer;
    tokenizer[strlen (tokenizer) - 1] = '\0'; // newline karakterini sil

    languages = realloc (languages, line_count);

    strcpy (languages[line_count - 1].name, lang_name);
    strcpy (languages[line_count - 1].code, lang_code);
  }
  return languages;
}

language.h

typedef struct language
{
  char* name;
  char* code;
} Language;

char* get_language_name_from_code (char* code);
char* get_language_code_from_name (char* name);
Language* get_available_languages ();

data/languages.txt

tr_TR=Türkçe
en_US=English

错误输出

[alperen@LAPTOP-EDN1J28M setup]$ make clean && make
Artıklar temizleniyor...
rm -rf lib/* *.o piluxsetup
gcc -c language.c -Iincludes -Llib -lsetup  -fPIC
gcc -c partition.c -Iincludes -Llib -lsetup  -fPIC
gcc language.o partition.o -Iincludes  -shared -o lib/libpiluxsetup.so
gcc -c main.c -Iincludes -Llib -lpiluxsetup  -fPIC
gcc main.o -Llib -lpiluxsetup -Iincludes  -o setup
[alperen@LAPTOP-EDN1J28M distsetup]$ ./setup 
--- Welcome to installer ---
Language: 
Parçalama arızası (çekirdek döküldü)
[alperen@LAPTOP-EDN1J28M distsetup]$ gdb ./setup 
GNU gdb (GDB) Fedora Linux 13.2-2.fc38
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

--Type <RET> for more, q to quit, c to continue without paging--
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./setup...

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n]) 
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in ./piluxsetup)
(gdb) run
Starting program: /home/alperen/Projeler/distsetup/piluxsetup 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
--- Welcome to installer ---
Language: 

Program received signal SIGSEGV, Segmentation fault.
__strcpy_evex () at ../sysdeps/x86_64/multiarch/strcpy-evex.S:223
223             vmovq   %VMM_128(0), (%rdi)

问题分析

段错误的核心原因集中在get_available_languages()函数:

  1. 内存分配错误:realloc(languages, line_count)仅分配了line_count字节,而非line_count * sizeof(Language),导致结构体数组内存不足,触发越界访问。
  2. 结构体指针未初始化:Language中的name和code是指针,但未分配内存,直接用strcpy写入会访问无效内存。
  3. 其他潜在问题:
    • get_language_name_from_code()和get_language_code_from_name()中,char *line未初始化就传入fgets,引发未定义行为;
    • 返回strtok结果时,指针指向栈上的line数组,文件关闭后栈内存被释放,返回的指针变为野指针;
    • 修改tokenizer[strlen(tokenizer)-1]前未检查tokenizer是否为空,若行格式错误会直接崩溃。

修复方案

修复后的get_available_languages()函数

Language* get_available_languages ()
{
  FILE *supported_languages = _open_languages_file ();

  int line_count = 0;
  char line[80];
  Language* languages = NULL;

  while (fgets (line, 80, supported_languages) != NULL)
  {
    // 跳过空行
    if (line[0] == '\n' || line[0] == '\0') continue;

    ++line_count;
    // 重新分配足够的结构体数组内存
    Language* temp = realloc(languages, line_count * sizeof(Language));
    if (!temp) {
      // 内存分配失败时清理已分配的内存
      for (int i = 0; i < line_count - 1; i++) {
        free(languages[i].name);
        free(languages[i].code);
      }
      free(languages);
      fclose(supported_languages);
      fprintf(stderr, "内存分配失败\n");
      exit(1);
    }
    languages = temp;

    char* tokenizer = strtok(line, "=");
    if (!tokenizer) continue; // 格式错误跳过
    // 为code分配内存并复制
    languages[line_count - 1].code = malloc(strlen(tokenizer) + 1);
    if (!languages[line_count - 1].code) { /* 错误处理同上 */ }
    strcpy(languages[line_count - 1].code, tokenizer);

    tokenizer = strtok(NULL, "=");
    if (!tokenizer) continue;
    // 去除换行符
    size_t len = strlen(tokenizer);
    if (len > 0 && tokenizer[len-1] == '\n') {
      tokenizer[len-1] = '\0';
      len--;
    }
    // 为name分配内存并复制
    languages[line_count - 1].name = malloc(len + 1);
    if (!languages[line_count - 1].name) { /* 错误处理同上 */ }
    strcpy(languages[line_count - 1].name, tokenizer);
  }

  fclose(supported_languages);
  return languages;
}

其他函数的补充修复

get_language_name_from_code()修复

char* get_language_name_from_code (char* code)
{
  FILE *supported_languages = _open_languages_file ();
  char line[80]; // 使用栈上的数组,而非未初始化的指针
  char* result = strdup("(not specified)");

  while (fgets (line, 80, supported_languages) != NULL)
  {
    if (strstr (line, code))
    {
      char* tokenizer = strtok (line, "=");
      if (tokenizer) {
        free(result);
        result = strdup(tokenizer); // 复制到堆内存,避免野指针
      }
      break;
    }
  }
  fclose(supported_languages);
  return result;
}

get_language_code_from_name()修复

char* get_language_code_from_name (char* name)
{
  FILE *supported_languages = _open_languages_file ();
  char line[80];
  char* result = strdup("(not specified)");

  while (fgets (line, 80, supported_languages) != NULL)
  {
    if (strstr (line, name))
    {
      char* tokenizer = strtok (line, "=");
      tokenizer = strtok (NULL, "=");
      if (tokenizer) {
        size_t len = strlen(tokenizer);
        if (len > 0 && tokenizer[len-1] == '\n') {
          tokenizer[len-1] = '\0';
        }
        free(result);
        result = strdup(tokenizer);
      }
      break;
    }
  }
  fclose(supported_languages);
  return result;
}

主函数的内存清理补充

使用完结构体数组后,需要逐个释放name和code的内存,再释放数组本身:

int main ()
{
  puts ("--- Welcome to PiluX CLI installer ---");
  puts ("Language: ");
  Language *languages = get_available_languages ();
  if (languages) {
    // 实际可修改函数返回行数,这里按已知两行处理
    for (int i = 0; i < 2; i++) {
      printf("%s: %s\n", languages[i].code, languages[i].name);
      free(languages[i].name);
      free(languages[i].code);
    }
    free(languages);
  }

  return 0;
}

关键修复点总结

  • 确保realloc分配的内存大小为元素数量 * sizeof(结构体);
  • 为结构体中的字符串指针分配堆内存,避免直接写入未初始化的指针;
  • 避免返回指向栈内存的指针,用strdup或malloc+strcpy将字符串复制到堆内存;
  • 添加错误处理逻辑,防止内存分配失败或文件格式错误导致崩溃;
  • 未初始化的指针必须先分配内存或指向有效地址再使用。

内容的提问来源于stack exchange,提问作者Alperen İsa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:52:31