C语言 如果没有n,为什么printf会产生分割错误?



为了学习多线程,我使用了老师给的代码,这对我的同学有用,但对我不起作用。

错误发生在这里:

void fonction2(){

int compteur1 = 1;
while(1){
fflush(stdout);
printf("je suis la coroutine1n");
fflush(stdout);
printf("blabla");
fflush(stdout);
printf("testn");
printf("test2n");
printf("compteur = %dn", compteur1);
fflush(stdout);
compteur1 = compteur1 +1;

//yield();//appel la coroutine2
exit(1);
}


}

我不只是在寻找如何使我的代码按预期工作,我想了解哪里出了问题。

我在Ubuntu1~22.04虚拟机中使用gcc 11.3.0进行编译,标记为-static -g - 0。

下面是我的全部代码:

#include<stdio.h>
#include<stdlib.h>
#define STACK_SIZE 4096
char pile1[STACK_SIZE] __attribute__((aligned(4096)));
char pile2[STACK_SIZE] __attribute__((aligned(4096)));
char pile3[STACK_SIZE] __attribute__((aligned(4096)));
char pile4[STACK_SIZE] __attribute__((aligned(4096)));
typedef void * coroutine_t;

void enter_coroutine(coroutine_t coroutine);
void switch_coroutine(coroutine_t *ct1, coroutine_t ct2);
coroutine_t coroutine_ordo;
coroutine_t coroutine4;
coroutine_t coroutine2;
coroutine_t coroutine3;
struct thread{
coroutine_t coroutine;
int statut; //O prêt 1 bloqué
};
struct thread thread2;
struct thread thread3;
struct thread thread4;
//struct liste_thread{
//  thread* premier;
//};
struct thread* thread_courant ;

//comme un push pour préparer le enter_coroutine
coroutine_t init_coroutine(void *stack_begin, unsigned int stack_size, void (*initial_pc)(void)){
char *stack_end = ((char *)stack_begin) + stack_size;
void* *ptr = (void**) stack_end;
ptr--;
*ptr = initial_pc; //program counter
ptr--;
*ptr = 0; //rbp
ptr--;
*ptr = 0; // rbx
ptr--;
*ptr = 0; //r12
ptr--;
*ptr = 0; //r13
ptr--;
*ptr = 0; //r14
ptr--;
*ptr = 0; //r15
return ptr;
}
void yield(void){
switch_coroutine(&thread_courant->coroutine, coroutine_ordo);
}


void fonction_ordo(){
while(1){
thread_courant = &thread2;
switch_coroutine(&coroutine_ordo, thread2.coroutine);
thread_courant = &thread3;
switch_coroutine(&coroutine_ordo, thread3.coroutine);
thread_courant = &thread4;
switch_coroutine(&coroutine_ordo, thread4.coroutine);
}

}
void fonction2(){

int compteur1 = 1;
while(1){
fflush(stdout);
printf("je suis la coroutine1n");
fflush(stdout);
printf("blablan");
fflush(stdout);
printf("testn");
printf("test2n");
printf("compteur = %dn", compteur1);
fflush(stdout);
compteur1 = compteur1 +1;

//yield();//appel la coroutine2
exit(1);
}


}
void fonction3(){
int compteur2 = 1;

while(1){
printf("je suis la coroutine3n");
printf("compteur = %dn",compteur2);
++compteur2;
yield();//appel la coroutine1;
}

}

void fonction4(){

int compteur2 = 1;
while(1){
printf("je suis la coroutine4n");
printf("compteur = %dn",compteur2);
++compteur2;
yield();//appel la coroutine1;
}

}
int main(){
setbuf(stdout, NULL);

coroutine_ordo = init_coroutine(pile1, STACK_SIZE, &fonction_ordo); //ordonnanceur !
printf("coroutine1 initn");
thread2.coroutine = init_coroutine(pile2, STACK_SIZE, &fonction2);
printf("coroutine2 initn");
thread3.coroutine = init_coroutine(pile3, STACK_SIZE, &fonction3);
printf("coroutine3 initn");
thread4.coroutine = init_coroutine(pile4, STACK_SIZE, &fonction4);
printf("coroutine4 initn");

enter_coroutine(coroutine_ordo);

return 0;
}

这是一个汇编文件,我也给编译器,其中堆栈指针被直接操作,从一个线程移动到另一个线程:

.global enter_coroutine, switch_coroutine
/* Note: le premier argument est dans ecx, le deuxieme dans edx. */
switch_coroutine:
push %rbp
push %rbx
push %r12
push %r13
push %r14
push %r15               
mov %rsp,(%rdi)         /* Store stack pointer to the coroutine pointer.. */
mov %rsi,%rdi           /* Continue to enter_coroutine, mais echange les arguments d'abord. */

enter_coroutine:
mov %rdi,%rsp         /* Load the stack pointer from the coroutine pointer. */
pop %r15
pop %r14
pop %r13
pop %r12
pop %rbx
pop %rbp
ret                   /* Pop the program counter. */

我试着修改代码来理解问题;在函数2中总是有一个分割错误,但这显示(其余代码中的其他内容)我是我的同事分割错误(core dump)

但是,如果我加上"n"紧接在& blabla&quot之后;然后显示(其他东西)我是我的同事鼓励性产业测试test2分割错误(core dump)

我认为使用fflush可以保证相同的行为,无论我给出什么参数printf?为什么在第一种情况下不显示测试打印?

唯一需要做的就是对init_coroutine做一个小改动,在协程堆栈的基础上添加一个虚拟的8字节值:

//comme un push pour préparer le enter_coroutine
coroutine_t init_coroutine(void *stack_begin, unsigned int stack_size, void (*initial_pc)(void)){
char *stack_end = ((char *)stack_begin) + stack_size;
void* *ptr = (void**) stack_end;
/* vvvvvvvvvvv */
ptr--;
*ptr = 0; // dummy value
/* ^^^^^^^^^^^ */
ptr--;
*ptr = initial_pc; //program counter
ptr--;
*ptr = 0; //rbp
ptr--;
*ptr = 0; // rbx
ptr--;
*ptr = 0; //r12
ptr--;
*ptr = 0; //r13
ptr--;
*ptr = 0; //r14
ptr--;
*ptr = 0; //r15
return ptr;
}

只要stack_end在16字节边界上(通过适当选择start_beginstack_size参数),这将使%RSP+8在每个函数开始时按照System V ABI对x86-64的要求对齐到16字节边界。

initial_pc值存储在16字节的边界上。在enter_coroutine()第一次执行ret指令后,这个initial_pc的值将在%RIP中,而%RSP+8将在ABI要求的16字节边界处。(如果协程函数调用另一个函数,它会将%RSP减少8字节的奇数倍,以便在调用该函数之前将%RSP保持在16字节的边界上。1)

-
1在堆栈上调用参数类型为__m256__m512的函数需要在call指令之前将%RSP对齐到32或64字节的边界。


原始代码出错的原因是GLIBC库函数在调用堆栈指针时假设堆栈指针已正确对齐。如果不是这样,不好的事情就会发生。

在传递给printf的字符串字面量(没有%字符)的末尾添加n可以产生差异的原因是GCC可能会将printf("blablan");转换为puts("blabla");,并且puts中的堆栈指针不对齐可能没有printf中的堆栈指针不对齐那么糟糕。(从printfputs的转换可以通过-fno-builtin-printfGCC编译器选项禁用。)

最新更新